Etienne Perot avatar Etienne Perot committed bc6bdf7

Updated steamodd

Comments (0)

Files changed (9)

+Anthony Garcia <lagg@lavabit.com>
 import os, json
-import tf2, tf2b, p2, user
+import tf2, tf2b, p2, d2, d2b, user
 
 _api_key = None
 
+"""
+Module for reading DOTA 2 data using the Steam API
+
+Copyright (c) 2010, Anthony Garcia <lagg@lavabit.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+"""
+
+import items
+
+_APP_ID = 570
+
+class backpack(items.backpack):
+    def __init__(self, sid, schema = None):
+        if not schema: schema = item_schema()
+        items.backpack.__init__(self, _APP_ID, sid, schema)
+
+class item_schema(items.schema):
+    def __init__(self, lang = None, lm = None):
+        items.schema.__init__(self, _APP_ID, lang, lm)
+"""
+Module for reading DOTA 2 (beta) data using the Steam API
+
+Copyright (c) 2010, Anthony Garcia <lagg@lavabit.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+"""
+
+import items
+
+_APP_ID = 816
+
+class backpack(items.backpack):
+    def __init__(self, sid, schema = None):
+        if not schema: schema = item_schema()
+        items.backpack.__init__(self, _APP_ID, sid, schema)
+
+class item_schema(items.schema):
+    def __init__(self, lang = None, lm = None):
+        items.schema.__init__(self, _APP_ID, lang, lm)
 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 """
 
-import json, os, urllib2, time, base, operator
+import json, os, urllib2, time, base, operator, re
 
 try:
     from collections import OrderedDict
         self.msg = msg
         self.asset = asset
 
-class schema(object):
+class HttpStale(Error):
+    """ Raised for HTTP code 304 """
+    def __init__(self, msg):
+        Error.__init__(self, msg)
+        self.msg = msg
+
+class HttpError(Error):
+    """ Raised for other HTTP codes or results """
+    def __init__(self, msg):
+        Error.__init__(self, msg)
+        self.msg = msg
+
+class base_json_request(object):
+    """ Base class for API requests over HTTP returning JSON """
+
+    def _get_download_url(self):
+        """ Returns the URL used for downloading the JSON data """
+        return self._url
+
+    def _download(self):
+        """ Standard download, does no additional checks """
+        res = urllib2.urlopen(self._get_download_url())
+        self._last_modified = res.headers.get("last-modified")
+        return res.read()
+
+    def _download_cached(self):
+        """ Uses self.last_modified """
+        req = urllib2.Request(self._get_download_url(), headers = {"If-Modified-Since": self.get_last_modified()})
+
+        try:
+            res = urllib2.urlopen(req)
+        except urllib2.HTTPError as e:
+            ecode = e.getcode()
+            if ecode == 304:
+                # No change
+                raise HttpStale(str(self.get_last_modified()))
+            else:
+                raise HttpError("HTTP error " + str(ecode))
+
+        return res.read()
+
+    def _deserialize(self, obj):
+        return json.loads(obj)
+
+    def get_last_modified(self):
+        """ Returns the last-modified header value """
+        return self._last_modified
+
+    def __init__(self, url, last_modified = None):
+        self._last_modified = last_modified
+        self._url = url
+
+class schema(base_json_request):
     """ The base class for the item schema. """
 
     def create_item(self, oitem):
         the value is a user friendly string. """
         return self._class_map
 
-    def _get_download_url(self):
-        """ Returns the URL to use for
-        fetching the raw schema """
+    def get_origin_name(self, origin):
+        """ Returns a localized origin name for a given ID """
 
-        return self._url
-
-    def _download(self):
-        return urllib2.urlopen(self._get_download_url()).read()
-
-    def _deserialize(self, schema):
-        # Convert the schema to a dict
-        return json.loads(schema)
+        return self._origins[int(origin)]["name"]
 
     def __iter__(self):
         return self.nextitem()
 
         return self.create_item(self._items[realkey])
 
-    def __init__(self, lang = None):
+    def __init__(self, appid, lang = None, lm = None):
         """ schema will be used to initialize the schema if given,
-        lang can be any ISO language code. """
+        lang can be any ISO language code.
+        lm will be used to generate an HTTP If-Modified-Since header. """
 
-        schema = None
-        if not lang: lang = "en"
+        self._language = lang or "en"
+        self._class_map = MapDict()
+        self._app_id = str(appid)
 
-        self._language = lang
-        self._url = ("http://api.steampowered.com/IEconItems_" + self._app_id +
-                     "/GetSchema/v0001/?key=" + base.get_api_key() + "&format=json&language=" + lang)
+        super(schema, self).__init__("http://api.steampowered.com/IEconItems_" + self._app_id +
+                                     "/GetSchema/v0001/?key=" + base.get_api_key() + "&format=json&language=" + self._language,
+                                     last_modified = lm)
 
-        schema = self._deserialize(self._download())
+        downloadfunc = None
+        if lm: downloadfunc = self._download_cached
+        else: downloadfunc = self._download
 
-        if not schema or schema["result"]["status"] != 1:
-            raise SchemaError("Schema error", schema["result"]["status"])
+        res = self._deserialize(downloadfunc())
+
+        if not res or res["result"]["status"] != 1:
+            raise SchemaError("Schema error", res["result"]["status"])
 
         self._attributes = {}
         self._attribute_names = {}
-        for attrib in schema["result"]["attributes"]:
+        for attrib in res["result"]["attributes"]:
             # WORKAROUND: Valve apparently does case insensitive lookups on these, so we must match it
             self._attributes[attrib["defindex"]] = attrib
             self._attribute_names[attrib["name"].lower()] = attrib["defindex"]
 
         self._items = {}
-        for item in schema["result"]["items"]:
+        for item in res["result"]["items"]:
             self._items[item["defindex"]] = item
 
         self._qualities = {}
-        for k,v in schema["result"]["qualities"].iteritems():
+        for k,v in res["result"]["qualities"].iteritems():
             aquality = {"id": v, "str": k, "prettystr": k}
 
-            try: aquality["prettystr"] = schema["result"]["qualityNames"][aquality["str"]]
+            try: aquality["prettystr"] = res["result"]["qualityNames"][aquality["str"]]
             except KeyError: pass
 
             self._qualities[v] = aquality
 
         self._particles = {}
-        for particle in schema["result"].get("attribute_controlled_attached_particles", []):
+        for particle in res["result"].get("attribute_controlled_attached_particles", []):
             self._particles[particle["id"]] = particle
 
         self._item_ranks = {}
-        for rankset in schema["result"].get("item_levels", []):
+        for rankset in res["result"].get("item_levels", []):
             self._item_ranks[rankset["name"]] = rankset["levels"]
 
         self._kill_types = {}
-        for killtype in schema["result"].get("kill_eater_score_types", []):
-            self._kill_types[killtype["type"]] = killtype["type_name"]
+        for killtype in res["result"].get("kill_eater_score_types", []):
+            self._kill_types[killtype["type"]] = killtype
+
+        self._origins = {}
+        for origin in res["result"].get("originNames", []):
+            self._origins[origin["origin"]] = origin
 
 class item:
     """ Stores a single TF2 backpack item """
         if not equipped: return []
 
         # Yes I'm stubborn enough to use this for a WORKAROUND
-        classes = set([classes.get(slot["class"]) for slot in
+        classes = set([classes.get(slot["class"], slot["class"]) for slot in
                        equipped if slot["class"] !=0 and slot["slot"] != 65535])
 
         return list(classes)
 
         return ((prefix or "") + " " + item_name + " " + suffix).strip()
 
+    def get_kill_eaters(self):
+        """
+        Returns a list of tuples containing the proper localized kill eater type strings and their values
+        according to set/type/value "order"
+        """
+
+        # Order matters in how they show up in the tuple
+        eaterspecs = {"type": "^kill eater user score type ?(?P<b>\d*)$|^kill eater score type ?(?P<a>\d*)$",
+                      "count": "^kill eater user ?(?P<b>\d*)$|^kill eater ?(?P<a>\d*)$"}
+        eaters = {}
+        finalres = []
+        ranktypes = self._schema.get_kill_types()
+
+        if not ranktypes:
+            return []
+
+        for attr in self:
+            for name, spec in eaterspecs.iteritems():
+                regexpmatch = re.match(spec, attr.get_name())
+                if regexpmatch:
+                    matchid = None
+                    value = int(attr.get_value())
+                    matchgroup = regexpmatch.groupdict()
+
+                    # Ensure no conflicts between ranking this and non-attached attributes
+                    for k, v in matchgroup.iteritems():
+                        if v != None:
+                            idsuffix = v or '0'
+                            matchid = k + idsuffix
+
+                    if matchid not in eaters:
+                        eaters[matchid] = {}
+
+                    eaters[matchid][name] = value
+                    eaters[matchid]["aid"] = attr.get_id()
+
+        for k in sorted(eaters.keys()):
+            eater = eaters[k]
+            rank = ranktypes[eater.get("type", 0)]
+            finalres.append((rank["level_data"], rank["type_name"], eater.get("count"), eater["aid"]))
+
+        return finalres
+
     def get_rank(self):
         """
         Returns the item's rank (if it has one)
         as a dict that includes required score, name, and level.
         """
 
-        kills = []
-
         if self._rank != {}:
             # Don't bother doing attribute lookups again
             return self._rank
 
-        try: kills.append(int(self["kill eater"].get_value()))
-        except KeyError: pass
+        eaterlines = self.get_kill_eaters()
 
-        try: kills.append(int(self["kill eater 2"].get_value()))
-        except KeyError: pass
-
-        if not kills:
+        if not eaterlines or eaterlines[0][2] == None:
             self._rank = None
             return None
+        else: eaterlines = eaterlines[0]
 
-        kills.sort(reverse = True)
-        
-        #WORKAROUND until it is possible to get the rank set name automatically
         ranksets = self._schema.get_kill_ranks()
-        rankset = []
-        if self.get_schema_id() == 655:
-            rankset = ranksets["SpiritOfGivingRank"]
-        else:
-            rankset = ranksets["KillEaterRank"]
-
+        rankset = ranksets[eaterlines[0]]
+        realranknum = eaterlines[2]
         for rank in rankset:
             self._rank = rank
-            if kills[0] < rank["required_score"]:
+            if realranknum < rank["required_score"]:
                 break
 
         return self._rank
         """ Assume this will change. For now returns a dict of various information about tool items """
         return self._schema_item.get("tool")
 
+    def get_origin_name(self):
+        """ Returns the item's localized origin name """
+
+        if "origin" in self._item:
+            return self._schema.get_origin_name(self._item["origin"])
+
+    def get_origin_id(self):
+        """ Returns the item's origin ID """
+
+        return self._item.get("origin")
+
     def __iter__(self):
         return self.nextattr()
 
         except TypeError:
             pass
 
-class backpack:
+class backpack(base_json_request):
     """ Functions for reading player inventory """
 
-    def load(self, sid):
-        """ Loads or refreshes the player backpack for the given steam.user
-        Returns a list of items, will be empty if there's nothing in the backpack"""
-        if not isinstance(sid, base.user.profile):
-            sid = base.user.profile(sid)
-        id64 = sid.get_id64()
-        url = ("http://api.steampowered.com/IEconItems_" + self._app_id + "/GetPlayerItems/"
-               "v0001/?key=" + base.get_api_key() + "&format=json&SteamID=")
-        inv = urllib2.urlopen(url + str(id64)).read()
-
-        # Once again I'm doing what Valve should be doing before they generate
-        # JSON. WORKAROUND
-        self._inventory_object = json.loads(inv.replace("-1.#QNAN0", "0"))
-        result = self._inventory_object["result"]["status"]
-        if result == 8:
-            raise Error("Bad SteamID64 given")
-        elif result == 15:
-            raise Error("Profile set to private")
-        elif result != 1:
-            raise Error("Unknown error")
-
-        itemlist = self._inventory_object["result"]["items"]
-        if len(itemlist) and itemlist[0] == None:
-            self._inventory_object["result"]["items"] = []
-
     def get_total_cells(self):
         """ Returns the total number of cells in the backpack.
         This can be used to determine if the user has bought a backpack
             iterindex += 1
             yield data
 
-    def __init__(self, sid = None, oschema = None):
+    def __init__(self, appid, sid, oschema = None):
         """ Loads the backpack of user sid if given,
         generates a fresh schema object if one is not given. """
 
         self._schema = oschema
+        self._app_id = str(appid)
+        self._profile = sid
+
+        if not isinstance(self._profile, base.user.profile):
+            self._profile = base.user.profile(self._profile)
+
+        url = ("http://api.steampowered.com/IEconItems_{0}/GetPlayerItems/v0001/?key={1}&format=json&SteamID={2}").format(
+            self._app_id,
+            base.get_api_key(),
+            self._profile.get_id64())
+
+        super(backpack, self).__init__(url)
+
         if not self._schema:
             self._schema = schema()
-        if sid:
-            self.load(sid)
 
-class assets(object):
-    """ Class for building asset catalogs """
+        # Once again I'm doing what Valve should be doing before they generate
+        # JSON. WORKAROUND
+        self._inventory_object = self._deserialize(self._download().replace("-1.#QNAN0", "0"))
+        result = self._inventory_object["result"]["status"]
+        if result == 8:
+            raise Error("Bad SteamID64 given")
+        elif result == 15:
+            raise Error("Profile set to private")
+        elif result != 1:
+            raise Error("Unknown error")
+
+        itemlist = self._inventory_object["result"]["items"]
+        if len(itemlist) and itemlist[0] == None:
+            self._inventory_object["result"]["items"] = []
+
+class asset_item:
+    def __init__(self, asset, catalog):
+        self._catalog = catalog
+        self._asset = asset
+
+    def __unicode__(self):
+        return self.get_name() + " " + str(self.get_price())
+
+    def __str__(self):
+        return unicode(self).encode("utf-8")
+
+    def get_tags(self):
+        """ Returns a dict containing tags and their localized labels as values """
+        tags = {}
+        for k in self._asset.get("tags"):
+            tags[k] = self._catalog._tag_map.get(k, k)
+        return tags
+
 
-    def get_price(self, assetindex, nonsale = False):
+    def get_price(self, nonsale = False):
         """ Returns a dict containing prices for all available
         currencies or a single price otherwise. If nonsale is
         True normal prices will always be returned, even if there
         is currently a discount """
 
-        try:
-            asset = self._assets[assetindex]
-            price = None
-            currency = self._currency
-            pricedict = asset["prices"]
-
-            if nonsale: pricedict = asset.get("original_prices", asset["prices"])
-
-            if currency:
-                try:
-                    price = float(pricedict[currency.upper()])/100
-                    return price
-                except KeyError:
-                    return None
-            else:
-                decprices = {}
-                for k, v in pricedict.iteritems():
-                    decprices[k] = float(v)/100
-                return decprices
-        except KeyError:
-            raise AssetError("Couldn't find asset " + str(assetindex))
+        asset = self._asset
+        price = None
+        currency = self._catalog._currency
+        pricedict = asset["prices"]
 
-    def get_tags(self, assetindex):
-        """ Returns a dict containing tags and their localized labels as values """
-        tags = {}
-        try:
-            asset = self._assets[assetindex]
-            for k in asset.get("tags").keys():
-                tags[k] = self._tag_map.get(k, k)
-        except KeyError:
-            raise AssetError("Couldn't find asset " + assetindex)
-        return tags
+        if nonsale: pricedict = asset.get("original_prices", asset["prices"])
 
-    def _get_download_url(self):
-        return self._url
+        if currency:
+            try:
+                price = float(pricedict[currency.upper()])/100
+                return price
+            except KeyError:
+                return None
+        else:
+            decprices = {}
+            for k, v in pricedict.iteritems():
+                decprices[k] = float(v)/100
+            return decprices
 
-    def _download(self):
-        return urllib2.urlopen(self._get_download_url()).read()
+    def get_name(self):
+        return self._asset.get("name")
 
-    def _deserialize(self, assets):
-        return json.loads(assets)
+class assets(base_json_request):
+    """ Class for building asset catalogs """
 
     def __getitem__(self, key):
         try:
-            return self.get_price(key.get_schema_id())
+            return self._assets[str(key.get_schema_id())]
         except:
-            try: return self.get_price(key)
-            except: raise KeyError(key)
+            return self._assets[str(key)]
+
+    def __iter__(self):
+        return self._nextitem()
 
-    def __init__(self, lang = None, currency = None):
+    def _nextitem(self):
+        data = sorted(self._assets.values(), key = asset_item.get_name)
+        iterindex = 0
+
+        while iterindex < len(data):
+            ydata = data[iterindex]
+            iterindex += 1
+            yield ydata
+
+    def __init__(self, appid, lang = None, currency = None, lm = None):
         """ lang: Language of asset tags, defaults to english
         currency: The iso 4217 currency code, returns all currencies by default """
 
-        if not lang: lang = "en"
-        self._language = lang
+        self._language = lang or "en"
         self._currency = currency
-        self._url = ("http://api.steampowered.com/ISteamEconomy/GetAssetPrices/v0001?" +
-                     "key={0}&format=json&language={1}&appid={2}".format(base.get_api_key(),
-                                                                         self._language,
-                                                                         self._app_id))
-        if self._currency: self._url += "&currency=" + self._currency
+        self._app_id = appid
+
+        url = ("http://api.steampowered.com/ISteamEconomy/GetAssetPrices/v0001?" +
+               "key={0}&format=json&language={1}&appid={2}".format(base.get_api_key(),
+                                                                   self._language,
+                                                                   self._app_id))
+        if self._currency: url += "&currency=" + self._currency
+
+        super(assets, self).__init__(url, lm)
+
+        downloadfunc = None
+        if lm: downloadfunc = self._download_cached
+        else: downloadfunc = self._download
+
+        res = self._deserialize(downloadfunc())
 
         try:
             adict = self._deserialize(self._download())["result"]
             self._tag_map = adict["tags"]
             self._assets = {}
             for asset in adict["assets"]:
-                for prop in asset["class"]:
-                    if prop.get("name") == "def_index":
-                        self._assets[int(prop.get("value"))] = asset
-                        break
+                self._assets[asset["name"]] = asset_item(asset, self)
         except KeyError as E:
             raise AssetError("Bad asset list")
 
 import items
 
-class backpack(items.backpack):
-    _app_id = "620"
+_APP_ID = 620
 
-    def __init__(self, sid = None, schema = None):
+class backpack(items.backpack):
+    def __init__(self, sid, schema = None):
         if not schema: schema = item_schema()
-        items.backpack.__init__(self, sid, schema)
+        items.backpack.__init__(self, _APP_ID, sid, schema)
 
 class item_schema(items.schema):
-    _app_id = "620"
-    _class_map = items.MapDict([
-            (1<<0, "P-body"),
-            (1<<1, "Atlas")
-            ])
-
     def create_item(self, oitem):
         return item(self, oitem)
 
-    def __init__(self, lang = None):
-        items.schema.__init__(self, lang)
+    def __init__(self, lang = None, lm = None):
+        items.schema.__init__(self, _APP_ID, lang, lm)
+
+        self._class_map = items.MapDict([
+                (1<<0, "P-body"),
+                (1<<1, "Atlas")
+                ])
 
 class item(items.item):
     def get_full_item_name(self, prefixes = None):
 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 """
 
-import items, urllib2, json, base, time
+import items, urllib2, json, base, time, re
+
+_APP_ID = 440
 
 class TF2Error(items.Error):
     def __init__(self, msg):
         self.msg = msg
 
 class item_schema(items.schema):
-    _app_id = "440"
-    _class_map = items.MapDict([
-            (1, "Scout"),
-            (3, "Soldier"),
-            (7, "Pyro"),
-            (4, "Demoman"),
-            (6, "Heavy"),
-            (9, "Engineer"),
-            (5, "Medic"),
-            (2, "Sniper"),
-            (8, "Spy")
-            ])
-
     def create_item(self, oitem):
         return item(self, oitem)
 
-    def __init__(self, lang = None):
-        items.schema.__init__(self, lang)
+    def __init__(self, appid = None, lang = None, lm = None):
+        items.schema.__init__(self, appid or _APP_ID, lang, lm)
+
+        self._class_map = items.MapDict([
+                (1, "Scout"),
+                (3, "Soldier"),
+                (7, "Pyro"),
+                (4, "Demoman"),
+                (6, "Heavy"),
+                (9, "Engineer"),
+                (5, "Medic"),
+                (2, "Sniper"),
+                (8, "Spy")
+                ])
+
 
 class backpack(items.backpack):
-    _app_id = "440"
+    def _deserialize(self, obj):
+        # WORKAROUND for truncated float_values in custom texture lo attributes
+        customtexturesub = re.sub('(\s*"float_value": -?\d+)\.[^\d]', '\\1.0', obj)
+        return json.loads(customtexturesub)
 
-    def __init__(self, sid = None, schema = None):
+    def __init__(self, sid, appid = None, schema = None):
         if not schema: schema = item_schema()
-        items.backpack.__init__(self, sid, schema)
+        items.backpack.__init__(self, appid or _APP_ID, sid, schema)
 
 class item(items.item):
     def get_equipable_classes(self):
         items.item.__init__(self, schema, item)
 
 class assets(items.assets):
-    _app_id = "440"
+    def __init__(self, appid = None, lang = None, currency = None, lm = None):
+        items.assets.__init__(self, appid or _APP_ID, lang, currency, lm)
 
-    def __init__(self, lang = None, currency = None):
-        items.assets.__init__(self, lang, currency)
+class golden_wrench_item:
+    def get_craft_date(self):
+        """ Returns the craft date as wrench as a time.struct_time object
+        as returned from localtime """
 
-class golden_wrench:
-    """ Functions for reading info for the golden wrenches found
-    during the Engineer update """
+        return time.localtime(self._wrench["timestamp"])
+
+    def get_id(self):
+        """ Returns the item ID (will match the ID in the user's inventory)
+        This is NOT the unique number from the wrench log, see get_serial
+        for that. """
+
+        return self._wrench["itemID"]
+
+    def get_craft_number(self):
+        """ Returns the number of the wrench in the order crafted """
 
+        return self._wrench["wrenchNumber"]
 
-    def _get_download_url(self):
-        return ("http://api.steampowered.com/ITFItems_440/GetGoldenWrenches/"
-                "v2/?key=" + base.get_api_key() + "&format=json")
+    def get_owner(self):
+        """ Returns the 64 bit ID of the wrench owner """
+
+        return self._wrench["steamID"]
 
-    def _download(self):
-        return urllib2.urlopen(self._get_download_url()).read()
+    def get_real_item(self):
+        """ Returns the "real" item of the wrench
+        this is an item compatible with steam.items.item classes
+        and can be used as such. Note that this
+        takes a while. Can be used to determine
+        if wrench was deleted. """
 
-    def _deserialize(self, wrenches):
-        return json.loads(wrenches)
+        if not self._real_item:
+            pack = backpack(self.get_owner())
+            for item in pack:
+                if item.get_id() == self.get_id():
+                    self._real_item = item
+                    return item
 
-    def get_wrenches(self):
-        """ Returns the list of wrenches """
+    def __init__(self, wrench):
+        self._real_item = None
+        self._wrench = wrench
 
-        return self._wrench_list
+class golden_wrench(items.base_json_request):
+    """ Functions for reading info for the golden wrenches found
+    during the Engineer update """
 
     def get_wrench_for_user(self, user):
         """ If the user found a wrench a gw object will be returned
         Otherwise None """
 
-        for w in self.get_wrenches():
-            if w["steamID"] == user.get_id64():
+        for w in self:
+            if w.get_owner() == user.get_id64():
                 return w
 
-    def get_craft_date(self, wrench):
-        """ Returns the craft date as wrench as a time.struct_time object
-        as returned from localtime """
+    def __iter__(self):
+        return self._nextitem()
 
-        return time.localtime(wrench["timestamp"])
+    def _nextitem(self):
+        iterindex = 0
+        data = sorted(set(self._idmap.values()), key = golden_wrench_item.get_craft_number)
 
-    def get_id(self, wrench):
-        """ Returns the item ID (will match the ID in the user's inventory)
-        This is NOT the unique number from the wrench log, see get_serial
-        for that. """
+        while iterindex < len(data):
+            ydata = data[iterindex]
+            iterindex += 1
+            yield ydata
 
-        return wrench["itemID"]
+    def __getitem__(self, key):
+        return self._idmap[key]
 
-    def get_craft_number(self, wrench):
-        """ Returns the number of the wrench in the order crafted """
+    def __init__(self):
+        """ Fetch the ever-useful and ever-changing wrench owner list """
+        url = ("http://api.steampowered.com/ITFItems_{0}/GetGoldenWrenches/"
+               + "v2/?key={1}&format=json").format(_APP_ID, base.get_api_key())
 
-        return wrench["wrenchNumber"]
+        super(golden_wrench, self).__init__(url)
 
-    def get_owner(self, wrench):
-        """ Returns the 64 bit ID of the wrench owner """
+        try:
+            self._idmap = {}
+            for wrench in self._deserialize(self._download())["results"]["wrenches"]:
+                rw = golden_wrench_item(wrench)
 
-        return wrench["steamID"]
-    
-    def __init__(self):
-        """ Will rewrite the wrench file if fresh = True """
+                self._idmap[rw.get_craft_number()] = rw
+                self._idmap[rw.get_id()] = rw
 
-        try:
-            self._wrench_list = self._deserialize(self._download())["results"]["wrenches"]
         except Exception as E:
             raise GoldenWrenchError("Failed to fetch wrench list: " + str(E))
 
 import tf2
 
-class item_schema(tf2.item_schema):
-    _app_id = "520"
+_APP_ID = 520
 
+class item_schema(tf2.item_schema):
     def _download(self):
         # WORKAROUND garbage characters
         return tf2.item_schema._download(self).replace("\xc4=", "Engineer")
 
-    def __init__(self, lang = None):
-        tf2.item_schema.__init__(self, lang)
+    def __init__(self, lang = None, lm = None):
+        tf2.item_schema.__init__(self, _APP_ID, lang, lm)
 
 class backpack(tf2.backpack):
-    _app_id = "520"
-
-    def __init__(self, sid = None, schema = None):
-        tf2.backpack.__init__(self, sid, schema)
+    def __init__(self, sid, schema = None):
+        if not schema: schema = item_schema()
+        tf2.backpack.__init__(self, sid, _APP_ID, schema)
         """ Attempts to fetch a profile assuming sid is a 64 bit ID """
         self._id64 = str(sid)
         try:
-            self._summary_object = self._deserialize(self._download())["response"]["players"]["player"][0]
+            self._summary_object = self._deserialize(self._download())["response"]["players"][0]
         except:
             raise ProfileError("Profile " + self._id64 + " (id64) not found")
 
     def __init__(self, sid = None):
         """ Creates a profile instance for the given user """
         self._profile_url = ("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/"
-                             "v0001/?key=" + base.get_api_key() + "&steamids=")
+                             "v0002/?key=" + base.get_api_key() + "&steamids=")
 
         if isinstance(sid, dict):
             self._summary_object = sid
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.