Skip to content

Commit

Permalink
refactor(steam): replace certain API calls to avoid rate limit (#20)
Browse files Browse the repository at this point in the history
Signed-off-by: hldh214 <hldh214@gmail.com>
  • Loading branch information
hldh214 committed Aug 25, 2023
1 parent d548c2b commit 54671a3
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 50 deletions.
51 changes: 26 additions & 25 deletions buff2steam/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
from buff2steam.provider.steam import Steam


def remove_exponent(d):
return d.quantize(decimal.Decimal(1)) if d == d.to_integral() else d.normalize()


async def main_loop(buff, steam):
total_page = await buff.get_total_page()
visited = set()
Expand All @@ -22,44 +18,49 @@ async def main_loop(buff, steam):
continue

market_hash_name = item['market_hash_name']
buff_min_price = remove_exponent(decimal.Decimal(item['sell_min_price']) * 100)
buff_says_steam_price = remove_exponent(decimal.Decimal(item['goods_info']['steam_price_cny']) * 100)
buff_min_price = int(decimal.Decimal(item['sell_min_price']) * 100)
buff_says_steam_price = int(decimal.Decimal(item['goods_info']['steam_price_cny']) * 100)

if not config['main']['max_price'] > buff_min_price > config['main']['min_price']:
logger.debug(f'{market_hash_name}: buff_min_price({buff_min_price}) not in range, skipping...')
continue

buff_says_ratio = buff_min_price / buff_says_steam_price if buff_says_steam_price else 1
accept_buff_threshold = decimal.Decimal(config['main']['accept_buff_threshold'])
accept_buff_threshold = config['main']['accept_buff_threshold']
if buff_says_ratio > accept_buff_threshold:
logger.debug(
f'{market_hash_name}: {buff_says_ratio} > {accept_buff_threshold}, skipping...'
)
continue

logger.debug(f'Processing {market_hash_name}...')
listings_data = await steam.listings_data(market_hash_name)
current_ratio = buff_min_price / listings_data['converted_price']
orders_data = await steam.orders_data(market_hash_name)
highest_buy_order = decimal.Decimal(orders_data['highest_buy_order'])
wanted_cnt = orders_data['wanted_cnt']
steam_tax_ratio = decimal.Decimal(listings_data['steam_tax_ratio'])
highest_buy_order_ratio = buff_min_price / (highest_buy_order * steam_tax_ratio)

price_overview_data = await steam.price_overview_data(market_hash_name)
if price_overview_data is None:
continue

volume = price_overview_data['volume']
orders_data = None
if volume > 0:
orders_data = await steam.orders_data(market_hash_name)

current_ratio = buff_min_price / (price_overview_data['price'] / (1 + steam.fee_rate))
buff_min_price_human = float(buff_min_price / 100)

result = [
f'buff_id/price: {item["id"]}/{buff_min_price_human};',
]

if orders_data is None:
result.append(f'volume: {volume}; ratio: {current_ratio:04.2f}')
else:
b_o_ratio = buff_min_price / (orders_data['highest_buy_order'] / (1 + steam.fee_rate))
result.append(f'sell/want: {orders_data["sell_order_count"]}/{orders_data["buy_order_count"]};')
result.append(f'volume: {volume}; b_o_ratio: {b_o_ratio:04.2f}; ratio: {current_ratio:04.2f}')

visited.add(item['id'])

logger.info(' '.join([
'buff_id/price: {buff_id}/{buff_price};'.format(
buff_id=item['id'], buff_price=buff_min_price_human
),
'sell/want: {sell}/{want};'.format(
sell=listings_data['total_count'], want=wanted_cnt
),
'b_o_ratio: {b_o_ratio:04.2f}; ratio: {ratio:04.2f}'.format(
b_o_ratio=highest_buy_order_ratio, ratio=current_ratio
)
]))
logger.info(' '.join(result))


async def main():
Expand Down
4 changes: 4 additions & 0 deletions buff2steam/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ class SteamAPI429Error(SteamError):

class SteamItemNameIdNotFoundError(SteamError):
pass


class BuffError(Exception):
pass
3 changes: 2 additions & 1 deletion buff2steam/provider/buff.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import httpx

from buff2steam import logger
from buff2steam.exceptions import BuffError


class Buff:
Expand Down Expand Up @@ -51,7 +52,7 @@ async def request(self, *args, **kwargs) -> dict:

response = await self.opener.request(*args, **kwargs)
if response.json()['code'] != 'OK':
raise Exception(response.json())
raise BuffError(response.json())

return response.json()['data']

Expand Down
53 changes: 30 additions & 23 deletions buff2steam/provider/steam.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import decimal
import re
import time

Expand All @@ -13,19 +14,19 @@

class Steam:
base_url = 'https://steamcommunity.com'
fee_rate = 0.15

web_sell = '/market/sellitem'
web_inventory = '/inventory/{steam_id}/{game_appid}/{context_id}'
web_listings = '/market/listings/{game_appid}/{market_hash_name}'
web_listings_render = web_listings + '/render'
web_item_orders_histogram = '/market/itemordershistogram'

item_nameid_pattern = re.compile(r'Market_LoadOrderSpread\(\s*(\d+)\s*\)')
wanted_cnt_pattern = re.compile(r'<span\s*class="market_commodity_orders_header_promote">(\d+)</span>')
currency_pattern = re.compile(r'[^\d.]')

def __init__(
self, asf_config=None, steam_id=None, game_appid='', context_id=2,
request_interval=30, request_kwargs=None
request_interval=4, request_kwargs=None
):
self.request_interval = request_interval
self.request_locks = {} # {caller: [asyncio.Lock, last_request_time]}
Expand All @@ -39,7 +40,6 @@ def __init__(
context_id=context_id
)
self.web_listings = self.web_listings.replace('{game_appid}', game_appid)
self.web_listings_render = self.web_listings_render.replace('{game_appid}', game_appid)

async def __aenter__(self):
return self
Expand Down Expand Up @@ -67,36 +67,37 @@ async def _request(self, *args, **kwargs):

return res

@tenacity.retry(retry=tenacity.retry_if_exception_type(buff2steam.exceptions.SteamAPI429Error))
async def _web_listings_render(self, market_hash_name):
return await self._request('GET', self.web_listings_render.format(market_hash_name=market_hash_name), params={
'count': 1,
'currency': 23
})

@tenacity.retry(retry=tenacity.retry_if_exception_type(buff2steam.exceptions.SteamAPI429Error))
async def _web_listings(self, market_hash_name):
return await self._request('GET', self.web_listings.format(market_hash_name=market_hash_name))

@tenacity.retry(retry=tenacity.retry_if_exception_type(buff2steam.exceptions.SteamAPI429Error))
async def _item_orders_histogram(self, item_nameid):
return await self._request('GET', self.web_item_orders_histogram, params={
'language': 'schinese',
'currency': 23,
'item_nameid': item_nameid,
'norender': 1,
'country': 'CN',
'two_factor': 0
})

async def _price_overview(self, market_hash_name):
return await self._request('GET', '/market/priceoverview', params={
'appid': self.game_appid,
'market_hash_name': market_hash_name,
'currency': 23,
})

async def listings_data(self, market_hash_name):
res = (await self._web_listings_render(market_hash_name)).json()
async def price_overview_data(self, market_hash_name):
res = (await self._price_overview(market_hash_name)).json()

listinginfo = res['listinginfo'][next(iter(res['listinginfo']))]
converted_price = listinginfo['converted_price']
converted_fee = listinginfo['converted_fee']
if 'lowest_price' not in res:
logger.debug(f'{market_hash_name}: {res}')
return None

return {
'converted_price': converted_price,
'total_count': res['total_count'],
'steam_tax_ratio': converted_price / (converted_price + converted_fee)
'price': int(decimal.Decimal(self.currency_pattern.sub('', res['lowest_price'])) * 100),
'volume': int(res.get('volume', '0').replace(',', '')),
}

async def orders_data(self, market_hash_name):
Expand All @@ -107,11 +108,17 @@ async def orders_data(self, market_hash_name):
logger.critical(res.text)
raise buff2steam.exceptions.SteamItemNameIdNotFoundError()

res = await self._item_orders_histogram(item_nameid[0])
try:
res = await self._item_orders_histogram(item_nameid[0])
except buff2steam.exceptions.SteamAPI429Error:
logger.debug(f'SteamAPI429Error({market_hash_name}): Could not get orders data, skipping...')
return None

orders_data = res.json()

return {
'highest_buy_order': orders_data['highest_buy_order'],
'wanted_cnt': self.wanted_cnt_pattern.findall(orders_data['buy_order_summary'])[0]
'sell_order_count': int(orders_data['sell_order_count'].replace(',', '')),
'buy_order_count': int(orders_data['buy_order_count'].replace(',', '')),
'lowest_sell_order': int(orders_data['lowest_sell_order']),
'highest_buy_order': int(orders_data['highest_buy_order']),
}
2 changes: 1 addition & 1 deletion config.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
}
},
"steam": {
"request_interval": 30,
"request_interval": 4,
"requests_kwargs": {
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
Expand Down

0 comments on commit 54671a3

Please sign in to comment.