Пример #1
0
class TradeManager(EventEmitter, ConfManager):
    """
    This is the TradeManager object, it inherits from the ConfManager and EventEmitter objects.
    """
    def __init__(self,
                 steamid,
                 key=None,
                 language='en',
                 identity_secret='',
                 poll_delay=30):
        EventEmitter.__init__(self)
        self.session = aiohttp.ClientSession()
        ConfManager.__init__(self, identity_secret, steamid, self.session)
        self.steamid = SteamID(steamid)
        if not self.steamid.isValid(
        ) or not self.steamid.type == SteamID.Type['INDIVIDUAL']:
            raise ValueError(
                f"Steam ID {self.steamid} is not valid, or is not a user ID")
        self.key = key
        self.language = language
        self.poll_delay = poll_delay
        self.logged_in = False
        self._trade_cache = {}
        self._conf_cache = {}

    async def login(self, async_client):
        """
        Take a AsyncClient object, using the do_login method is optional.
        You must have passed in all credentials and requirements to the client already.
        Please make sure to run this first, just like in the example.py

        :param async_client: Does not need to be logged in, import from pytrade.login
        """
        if async_client.logged_in:
            self.session = async_client.session
            self.logged_in = True
        else:
            session = await async_client.do_login()
            if await async_client.test_login():
                self.session = session
                self.logged_in = True
            else:
                raise ValueError("Login Failed")
        self.emit('logged_on')

    async def _run_forever(self):
        while True:
            self.emit('start_poll')
            #Check for current actions, then parse them
            await self._trade_poll()
            await self._confirmation_poll()
            self.emit('end_poll')
            await asyncio.sleep(self.poll_delay)

    def run_forever(self):
        """
        Run the bot forever, unless the time sense calling the function is greater than timeout.

        :param timeout: :class int/float:
        :return: (Will never return, but if it does, None)
        """
        loop = asyncio.get_event_loop()
        asyncio.ensure_future(self._run_forever())
        loop.run_forever()

    @require_key
    async def get_trade_offers(self,
                               active_only=True,
                               sent=False,
                               received=True):
        """
        get your trade offers, key is required.

        :param active_only:
        :param sent:
        :param received:
        :return trade_list:
        """
        offers = await self.api_call('GET',
                                     'IEconService',
                                     'GetTradeOffers',
                                     'v1',
                                     langauge=self.language,
                                     get_descriptions=1,
                                     active_only=1,
                                     get_sent_offers=1,
                                     get_received_offers=1,
                                     key=self.key)
        sent_offers = []
        got_offers = []
        trade_offers = {}

        if sent:
            for offer in offers['response'].get('trade_offers_sent', []):
                trade_offer = TradeOffer(
                    offer, offers['response'].get('descriptions', []), self)
                if trade_offer.trade_offer_state != ETradeOfferState.Active and active_only:
                    continue
                sent_offers.append(trade_offer)
            trade_offers['sent'] = sent_offers

        if received:
            for offer in offers['response'].get('trade_offers_received', []):
                trade_offer = TradeOffer(
                    offer, offers['response'].get('descriptions', []), self)
                if trade_offer.trade_offer_state != ETradeOfferState.Active and active_only:
                    continue
                got_offers.append(trade_offer)
            trade_offers['received'] = got_offers

        return trade_offers

    async def _trade_poll(self):
        #First, check for new trades
        trades = await self.get_trade_offers(active_only=False,
                                             sent=True,
                                             received=True)
        got_trades = trades.get('received', [])
        sent_trades = trades.get('sent', [])
        for trade in got_trades:
            if trade.tradeofferid not in self._trade_cache.keys():
                self._trade_cache[trade.tradeofferid] = trade
                if trade.trade_offer_state == ETradeOfferState.Active:
                    self.emit('new_trade', trade)
            else:
                self._test_states(trade)
        for trade in sent_trades:
            if trade.tradeofferid not in self._trade_cache.keys():
                self._trade_cache[trade.tradeofferid] = trade
                self.emit('trade_sent', trade)
            else:
                self._test_states(trade)

    async def _confirmation_poll(self):
        confs = await self.get_confirmations()
        for conf in confs:
            if conf.id not in self._conf_cache.keys():
                self._conf_cache[conf.id] = conf
                self.emit('new_conf', conf)

    def _test_states(self, trade):
        if trade.trade_offer_state != self._trade_cache[
                trade.tradeofferid].trade_offer_state:
            self._trade_cache[trade.tradeofferid] = trade
            if trade.trade_offer_state == ETradeOfferState.Accepted:
                self.emit('trade_accepted', trade)
            elif trade.trade_offer_state == ETradeOfferState.Canceled:
                self.emit('trade_canceled', trade)
            elif trade.trade_offer_state == ETradeOfferState.Declined:
                self.emit('trade_declined', trade)
            elif trade.trade_offer_state == ETradeOfferState.Expired:
                self.emit('trade_expired', trade)
            elif trade.trade_offer_state == ETradeOfferState.Countered:
                self.emit('trade_countered', trade)
            else:
                self.emit('trade_state_changed', trade)

    async def api_call(self, method, api, call, version, **data):
        """
        Request data from steam through this function when using the API
        :param method:
        :param api:
        :param call:
        :param version:
        :kwargs data:
        :return json:
        """
        new_url = SteamUrls.Api.value + '/'.join(['', api, call, version])
        if method.lower() == 'get':
            async with self.session.get(new_url, params=data) as resp:
                #print(await resp.json())
                return await resp.json()  #Should be json
        elif method.lower() == 'post':
            async with self.session.post(new_url, data=data) as resp:
                return await resp.json()
        elif method.lower() == 'delete':
            async with self.session.delete(new_url, data=data) as resp:
                return await resp.json()
        elif method.lower() == 'put':
            async with self.session.put(new_url, data=data) as resp:
                return await resp.json()
        else:
            raise ValueError(f"Invalid method: {method}")

    def get_session(self):
        return self.session.cookie_jar._cookies['steamcommunity.com'][
            'sessionid'].value

    def parse_token_from_url(self, trade_offer_url: str):
        reg = re.compile(
            "https?:\/\/(www.)?steamcommunity.com\/tradeoffer\/new\/?\?partner=\d+(&|&amp;)token=(?P<token>[a-zA-Z0-9-_]+)"
        )
        match = reg.match(trade_offer_url)
        if not match:
            raise Exception("Unable to match token from trade_offer_url")
        return match['token']

    async def get_inventory(self,
                            steamid: SteamID,
                            appid,
                            contextid=2,
                            tradable_only=1):
        """
        Get a user's inventory

        :param steamid:
        :param appid:
        :param contextid:
        :param tradable_only:
        :return:
        """
        async with self.session.get(
                f"https://steamcommunity.com/profiles/{steamid.toString()}/inventory/json/{appid}/{contextid}",
                params={'trading': tradable_only},
                headers=
            {
                "Referer":
                f"https://steamcommunity.com/profiles/{steamid.toString()}/inventory"
            }) as resp:
            inv = await resp.json()

        if not inv['success']:
            return []

        items = []
        for _, item_id in inv['rgInventory'].items():
            id = item_id['classid'] + '_' + item_id['instanceid']
            item_desc = inv['rgDescriptions'].get(id)
            if item_desc is None:
                items.append(Item(item_id, True))
            items.append(Item(merge_item(item_id, item_desc)))
        return items
Пример #2
0
class TradeManager(EventEmitter, ConfManager):

    """
    This is the TradeManager object, it inherits from the ConfManager and EventEmitter objects.
    """

    def __init__(self, steamid, key=None, language: str='en', identity_secret: str='', poll_delay: int=30,
                 login_delay_time: int=0):
        """

        :param steamid: stemid64
        :param key: steam api key
        :param language:
        :param identity_secret:
        :param poll_delay: how often trades should be polled (too often can cause errors, too infrequent can make your
        bot too slow to respond
        :param login_delay_time: how long to wait after our session died to retry
        """
        EventEmitter.__init__(self)
        self.session = aiohttp.ClientSession()
        ConfManager.__init__(self, identity_secret, steamid, self.session)
        self.steamid = SteamID(steamid)
        if not self.steamid.isValid() or not self.steamid.type == SteamID.Type['INDIVIDUAL']:
            raise ValueError(f"Steam ID {self.steamid} is not valid, or is not a user ID")
        self.key = key
        self.language = language
        self.poll_delay = poll_delay
        self.last_poll = 0
        self.logged_in = False
        self._trade_cache = {}
        self._conf_cache = {}
        self.first_run = True
        self.login_delay_time = login_delay_time

    async def login(self, async_client):
        """
        Take a AsyncClient object, using the do_login method is optional.
        You must have passed in all credentials and requirements to the client already.
        Please make sure to run this first, just like in the example.py

        :param async_client: Does not need to be logged in, import from pytrade.login
        """
        if self.first_run:
            self.async_client = copy(async_client)
            self.first_run = False
        
        if async_client.logged_in:
            self.session = async_client.session
            self.logged_in = True
        else:
            session = await async_client.do_login()
            if await async_client.test_login():
                self.session = session
                self.logged_in = True
            else:
                raise ValueError("Login Failed")
        self.emit("logged_on")

    async def poll(self):
        if time() - self.last_poll > self.poll_delay:
            self.emit("trade_start_poll")
            await self._trade_poll()
            await self._confirmation_poll()
            self.emit("trade_end_poll")
            self.last_poll = time()

    @require_key
    async def get_trade_offers(self, active_only=True, sent=False, received=True):
        """
        Get your trade offers, key is required.

        :param active_only:
        :param sent:
        :param received:
        :return trade_list:
        """
        
        try:
            offers = await self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', langauge=self.language,
                                         get_descriptions=1, active_only=1, get_sent_offers=1, get_received_offers=1,
                                         key=self.key)
        except ValueError:
            await self.login(self.async_client)
            offers = await self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', langauge=self.language,
                                         get_descriptions=1, active_only=1, get_sent_offers=1, get_received_offers=1,
                                         key=self.key)
        except (aiohttp.client_exceptions.ClientOSError, aiohttp.client_exceptions.ServerDisconnectedError):
            # aiohttp.client_exceptions.ClientOSError:
            # [WinError 10054] An existing connection was forcibly closed by the remote host
            offers = await self.api_call('GET', 'IEconService', 'GetTradeOffers', 'v1', langauge=self.language,
                                         get_descriptions=1, active_only=1, get_sent_offers=1, get_received_offers=1,
                                         key=self.key)
        if offers[0]:
            offers = offers[1]
        else:
            return False, offers[1]
        
        sent_offers = []
        got_offers = []
        trade_offers = {}

        if sent:
            for offer in offers['response'].get('trade_offers_sent', []):
                trade_offer = TradeOffer(offer, offers['response'].get('descriptions', []), self)
                if trade_offer.trade_offer_state != ETradeOfferState.Active and active_only:
                    continue
                sent_offers.append(trade_offer)
            trade_offers['sent'] = sent_offers

        if received:
            for offer in offers['response'].get('trade_offers_received', []):
                trade_offer = TradeOffer(offer, offers['response'].get('descriptions', []), self)
                if trade_offer.trade_offer_state != ETradeOfferState.Active and active_only:
                    continue
                got_offers.append(trade_offer)
            trade_offers['received'] = got_offers

        return True, trade_offers

    async def _trade_poll(self):
        trades = await self.get_trade_offers(True, True, True)
        
        if not trades[0]:
            self.emit('trade_poll_error', trades[1])
            return
        
        trades = trades[1]
        got_trades = trades.get('received', [])
        sent_trades = trades.get('sent', [])
        
        for trade in got_trades:
            if trade.tradeofferid not in self._trade_cache.keys():
                self._trade_cache[trade.tradeofferid] = trade
                if trade.trade_offer_state == ETradeOfferState.Active:
                    self.emit('new_trade', trade)
            else:
                self._test_states(trade)
        for trade in sent_trades:
            if trade.tradeofferid not in self._trade_cache.keys():
                self._trade_cache[trade.tradeofferid] = trade
                self.emit('trade_sent', trade)
            else:
                self._test_states(trade)

    async def _confirmation_poll(self):
        confs = await self.get_confirmations()
        
        if not confs[0]:
            self.emit('trade_poll_error', confs[1])
            return
            
        for conf in confs[1]:
            if conf.id not in self._conf_cache.keys():
                self._conf_cache[conf.id] = conf
                self.emit('new_conf', conf)

    def _test_states(self, trade):
        if trade.trade_offer_state != self._trade_cache[trade.tradeofferid].trade_offer_state:
            self._trade_cache[trade.tradeofferid] = trade
            if trade.trade_offer_state == ETradeOfferState.Accepted:
                self.emit('trade_accepted', trade)
            elif trade.trade_offer_state == ETradeOfferState.Canceled:
                self.emit('trade_canceled', trade)
            elif trade.trade_offer_state == ETradeOfferState.Declined:
                self.emit('trade_declined', trade)
            elif trade.trade_offer_state == ETradeOfferState.Expired:
                self.emit('trade_expired', trade)
            elif trade.trade_offer_state == ETradeOfferState.Countered:
                self.emit('trade_countered', trade)
            else:
                self.emit('trade_state_changed', trade)

    async def api_call(self, method, api, call, version, **data):
        """
        Request data from steam through this function when using the API
        :param method:
        :param api:
        :param call:
        :param version:
        :kwargs data:
        :return json:
        """
        new_url = SteamUrls.Api.value + '/'.join(['', api, call, version])
        try:
            if method.lower() == 'get':
                async with self.session.get(new_url, params=data) as resp:
                    j = await resp.json()
                    return True, j
            elif method.lower() == 'post':
                async with self.session.post(new_url, data=data) as resp:
                    j = await resp.json()
                    return True, j
            elif method.lower() == 'delete':
                async with self.session.delete(new_url, data=data) as resp:
                    j = await resp.json()
                    return True, j
            elif method.lower() == 'put':
                async with self.session.put(new_url, data=data) as resp:
                    j = await resp.json()
                    return True, j
            else:
                raise ValueError(f"Invalid method: {method}")
        except aiohttp.ContentTypeError:
            html = await resp.text()
            return False, html

    def get_session(self):
        return self.session.cookie_jar._cookies['steamcommunity.com']['sessionid'].value

    def parse_token_from_url(self, trade_offer_url: str):
        reg = re.compile("https?:\/\/(www.)?steamcommunity.com\/tradeoffer\/new\/?\?partner=\d+(&|&amp;)token=(?P<token>[a-zA-Z0-9-_]+)")
        match = reg.match(trade_offer_url)
        if not match:
            return False, match
        return True, match['token']

    async def get_inventory(self, steamid: SteamID, appid, contextid=2, tradable_only=1):
        """
        Get a user's inventory

        :param steamid:
        :param appid:
        :param contextid:
        :param tradable_only:
        :return:
        """
        url = f"https://steamcommunity.com/profiles/{steamid.toString()}/inventory/json/{appid}/{contextid}"
        params = {'trading': tradable_only}
        headers = {"Referer": f"https://steamcommunity.com/profiles/{steamid.toString()}/inventory"}
        async with self.session.get(url, params=params, headers=headers) as resp:
                inv = await resp.json()

        if not inv['success']:
            return False, inv

        items = []
        for _, item_id in inv['rgInventory'].items():
            id = item_id['classid'] + '_' + item_id['instanceid']
            item_desc = inv['rgDescriptions'].get(id)
            if item_desc is None:
                items.append(Item(item_id, True))
            items.append(Item(merge_item(item_id, item_desc)))
        return True, items