Skip to content

Instantly share code, notes, and snippets.

@iamcsharper
Created December 20, 2015 14:53
Show Gist options
  • Save iamcsharper/1d4dd46ee8ab56e68adf to your computer and use it in GitHub Desktop.
Save iamcsharper/1d4dd46ee8ab56e68adf to your computer and use it in GitHub Desktop.
This will let you parse all the items from Steam inventory very fast!
<?php
abstract class BaseCollection
{
/**
* rgDescriptions array
* @var array
*/
protected $items = [];
/**
* Each element is implementing ItemCollectionFilter!
* @see ItemCollectionFilter
* @var array
*/
protected $filters = [];
/**
* @var ICollectionParser
*/
protected $collectionParser;
/**
* @param array $items
*/
public function __construct(array $items)
{
$this->items = $items;
}
public function getUniqueKey()
{
return md5(json_encode($this->items) . json_encode($this->filters));
}
/**
* Returns {@link Item} array
* @see Item
* @return array
*/
public function get()
{
return $this->collectionParser->parseCollection($this);
}
/**
* Returns first row
* @return mixed
*/
public function first()
{
$this->filters[] = new SliceCollectionFilter(0, 1);
return $this->get()[0];
}
public function add($value)
{
$this->items[] = $value;
}
public function getFilters()
{
return $this->filters;
}
// Bad code
public function __call($name, $arguments)
{
if (strpos($name, 'findBy') !== false) {
$string = strtolower(str_replace('findBy', '', $name));
foreach ($this->items as $item) {
if ($item[$string] == $arguments[0]) {
return $this->collectionParser->parseItem($item);
}
}
}
}
public function toArray()
{
return array_values($this->items);
}
public function toJSON()
{
return json_encode($this->toArray(), JSON_PRETTY_PRINT);
}
public function isEmpty()
{
return !$this->size();
}
public function size()
{
return count($this->items);
}
public function getItems()
{
return $this->items;
}
public function setItems(array $items)
{
$this->items = $items;
}
public function removeAt($i)
{
$index = 0;
foreach ($this->items as $k => $v) {
// Numeric or associative
if ($index == $i || $k == $i) {
unset($this->items[$k]);
break;
}
++$index;
}
}
public function removeAtMany(array $ids)
{
$index = 0;
foreach ($this->items as $k => $v) {
// Numeric or associative
if (in_array($index, $ids) || in_array($k, $ids)) {
unset($this->items[$k]);
}
++$index;
}
}
}
<?php
interface BaseCollectionFilter {
/**
* @param BaseCollection $collection
* @return array
*/
public function execute(BaseCollection &$collection);
}
<?php
trait CollectionMethods {
public function slice($a, $b)
{
$this->filters[] = new SliceCollectionFilter($a, $b);
return $this;
}
/**
* @param \Closure $closure
* @return $this
*/
public function where(\Closure $closure)
{
$this->filters[] = new WhereCollectionFilter($closure);
return $this;
}
}
<?php
class DotaItem extends Item
{
// Always is static for Dota items
public static $contextId = 2;
protected $appId = 570;
protected static $dotaRaritiesEnum = [
'common',
'uncommon',
'rare',
'mythical',
'legendary',
'immortal',
'arcana',
'ancient'
];
private static $dotaTypesEnum = [
'Wearable',
'HUD Skin',
'Loading Screen',
'Announcer',
'Courier',
'Tool',
'Taunt',
'Gem / Rune',
'Bundle'
];
private static $dotaQualitiesEnum = [
'Infused',
'Normal',
'Auspicious',
'Inscribed',
'Heroic',
'Genuine',
'Cursed',
'Frozen',
'Unusual',
'Elder',
'Corrupted',
'Autographed',
'Exalted',
'Legacy',
'Ascendant',
'Favored',
];
private static $_heroes = [];
public static function getHeroes()
{
// Get from steam, change these
if (empty(self::$_heroes)) {
$api = new APIBridge(config('steam-auth.api_key'));
$json = $api->callApi('IEconDOTA2_570/', 'GetHeroes/v0001/', [
'language' => 'russian'
]);
$result = json_decode($json, true);
$heroes = $result['result']['heroes'];
self::$_heroes = $heroes;
}
return self::$_heroes;
}
private $rarity;
private $quality;
public function __construct(array $data)
{
parent::__construct($data);
foreach ($this->tags as $tag) {
if (strtolower($tag['category']) == 'rarity') {
$this->rarity = strtolower($tag['name']);
} elseif (strtolower($tag['category']) == 'hero') {
$this->characters[] = $tag['name'];
} elseif (strtolower($tag['category']) == 'quality') {
$this->quality = $tag['name'];
}
}
}
public function getRarity()
{
return $this->rarity;
}
public function getHero()
{
return $this->characters[0];
}
private function getRarityOrder()
{
return self::$dotaRaritiesEnum[$this->getRarity()];
}
private static function checkBeforeSort($a, $b)
{
$badType = false;
if (!($a instanceof DotaItem))
$badType = gettype($a);
if (!($b instanceof DotaItem))
$badType = gettype($b);
if ($badType)
throw new \RuntimeException('Incompatible types. Excepted: ' . self::class . '. Got: ' . $badType);
}
/**
* @param $a DotaItem
* @param $b DotaItem
* @return int
*/
public static function sortByQuality($a, $b)
{
static::checkBeforeSort($a, $b);
$al = (!isset(self::$dotaQualitiesEnum[$a->getQuality()])) ? 0 : self::$dotaQualitiesEnum[$a->getHero()];
$bl = (!isset(self::$dotaQualitiesEnum[$b->getQuality()])) ? 0 : self::$dotaQualitiesEnum[$b->getHero()];
if ($al == $bl)
return 0;
return ($al > $bl) ? -1 : 1;
}
/**
* @param $a DotaItem
* @param $b DotaItem
* @return int
*/
public static function sortByType($a, $b)
{
static::checkBeforeSort($a, $b);
$al = self::$dotaTypesEnum[$a->getType()];
$bl = self::$dotaTypesEnum[$b->getType()];
if ($al == $bl) {
return 0;
}
return ($al > $bl) ? -1 : 1;
}
/**
* @param $a DotaItem
* @param $b DotaItem
* @return int
*/
public static function sortByCharacter($a, $b)
{
static::checkBeforeSort($a, $b);
$al = $bl = 0;
foreach (self::getHeroes() as $hero) {
if ($hero['localized_name'] == $a->getHero())
$al = true;
elseif ($hero['localized_name'] == $b->getHero())
$bl = true;
}
if (!$al) {
return 1;
}
if (!$bl) {
return -1;
}
return strnatcmp($a->getHero(), $b->getHero());
}
/**
* @param $a DotaItem
* @param $b DotaItem
* @return int
*/
public static function sortByPrice($a, $b)
{
static::checkBeforeSort($a, $b);
$al = $a->getPrice();
$al = ($al) ? $al->getSitePrice() : 0;
$bl = $b->getPrice();
$bl = ($bl) ? $bl->getSitePrice() : 0;
if ($al == $bl) {
return 0;
}
return ($al > $bl) ? -1 : 1;
}
/**
* @param $a DotaItem
* @param $b DotaItem
* @return int
*/
public static function sortByRarity($a, $b)
{
static::checkBeforeSort($a, $b);
$al = $a->getRarityOrder();
$bl = $b->getRarityOrder();
if ($al == $bl) {
return 0;
}
return ($al > $bl) ? -1 : 1;
}
/**
* @return mixed
*/
public function getQuality()
{
return $this->quality;
}
/**
* @param mixed $quality
*/
public function setQuality($quality)
{
$this->quality = $quality;
}
}
<?php
interface ICollectionParser
{
public function parseCollection(BaseCollection &$collection);
public function parseItem($item);
}
<?php
abstract class Item
{
public static $contextId;
/**
* Item name
* @var string
*/
protected $name;
/**
* #Color of the item
* @var string
*/
protected $nameColor;
/**
* Item's market name
* @var string
*/
protected $marketName;
/**
* id of the application, which item is added to
* @var integer
*/
protected $appId;
/**
* Item class id
* @var integer
*/
protected $classId;
/**
* Item instance id
* @var integer
*/
protected $instanceId;
/**
* Hash for url linked to to item's image
* @see http://cdn.steamcommunity.com/economy/image/
* @var string
*/
protected $iconUrl;
/**
* Hash for url linked to to item's bigimage
* @see http://cdn.steamcommunity.com/economy/image/
* @var string
*/
protected $iconUrlLarge;
/**
* Item type
* @var string
*/
protected $type;
/**
* Can we trade with it?
* @var boolean
*/
protected $tradable;
/**
* Can we sell this item on market?
* @var boolean
*/
protected $marketable;
/**
* Item's descriptions
* @var array
*/
protected $descriptions = [];
/**
* Item's tags
* @var array
*/
protected $tags = [];
/**
* Characters who can use this item
* @var array
*/
protected $characters = [];
/**
* Calculated in ItemPrice
* @see ItemCollection
* @var ItemPrice
*/
protected $price;
public function __construct(array $data)
{
$this->appId = $data['appid'];
$this->classId = $data['classid'];
$this->instanceId = $data['instanceid'];
$this->iconUrl = $data['icon_url'];
$this->iconUrlLarge = $data['icon_url_large'];
$this->name = $data['name'];
$this->marketName = $data['market_hash_name'];
$this->nameColor = $data['name_color'];
$this->tradable = $data['tradable'];
$this->marketable = $data['marketable'];
if (is_array($data['descriptions'])) {
foreach ($data['descriptions'] as $description) {
if (preg_replace('/\s+/', '', $description['value']) == '')
continue;
$this->descriptions[] = $description['value'];
}
}
if (is_array($data['tags']))
$this->tags = $data['tags'];
foreach ($this->tags as $tag) {
if (strtolower($tag['category_name']) == 'type') {
$this->type = $tag['name'];
}
}
if ($data['type'] != '')
$this->type = $data['type'];
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* @return string
*/
public function getNameColor()
{
return $this->nameColor;
}
/**
* @param string $nameColor
*/
public function setNameColor($nameColor)
{
$this->nameColor = $nameColor;
}
/**
* @return string
*/
public function getMarketName()
{
return $this->marketName;
}
/**
* @return int
*/
public function getAppId()
{
return $this->appId;
}
/**
* @return int
*/
public function getClassId()
{
return $this->classId;
}
/**
* @return int
*/
public function getInstanceId()
{
return $this->instanceId;
}
/**
* @return string
*/
public function getIconUrl()
{
return $this->iconUrl;
}
/**
* @return string
*/
public function getIconUrlLarge()
{
return $this->iconUrlLarge;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return boolean
*/
public function isTradable()
{
return $this->tradable;
}
/**
* @return boolean
*/
public function isMarketable()
{
return $this->marketable;
}
/**
* @return array
*/
public function getDescriptions()
{
return $this->descriptions;
}
/**
* @return array
*/
public function getTags()
{
return $this->tags;
}
/**
* Link to a transparent png image of item
* @return string
*/
public function getClearUrl()
{
return 'https://steamcommunity-a.akamaihd.net/economy/image/class/' . $this->appId . '/' . $this->classId . '/';
}
/**
* @return ItemPrice
*/
public function getPrice()
{
return $this->price;
}
/**
* @param ItemPrice $price
*/
public function setPrice(ItemPrice $price)
{
$this->price = $price;
}
public function getSitePrice() {
if (is_null($this->price))
return 0;
return $this->price->getSitePrice();
}
abstract public function getRarity();
/*
* Just to let children override all these methods
*/
static public function sortByRarity($rarityA, $rarityB)
{
return 0;
}
static public function sortByQuality($a, $b)
{
return 0;
}
static public function sortByType($typeA, $typeB)
{
return 0;
}
static public function sortByCharacter($characterA, $characterB)
{
return 0;
}
static public function sortByPrice($priceA, $priceB)
{
return 0;
}
}
<?php
/**
* That's it!
**/
class ItemCollection extends BaseCollection
{
use CollectionMethods;
static $searches = [];
public function __construct(array $items, ItemMarketPriceHelper $marketPriceHelper) {
parent::__construct($items);
$this->collectionParser = new ItemCollectionParser($marketPriceHelper);
}
public function __call($name, $arguments)
{
if (strpos($name, 'findBy') !== false) {
$string = strtolower(str_replace('findBy', '', $name));
if (!empty(self::$searches[$string]))
return self::$searches[$string];
foreach ($this->items as $item) {
if ($item[$string] == $arguments[0]) {
$parsed = $this->collectionParser->parseItem($item);
self::$searches[$string] = $parsed;
return $parsed;
}
}
}
}
}
<?php
class ItemCollectionParser implements ICollectionParser
{
private static $collectionCache;
private static $itemCache;
/**
* @var ItemMarketPriceHelper
*/
protected $priceHelper;
public function __construct(ItemMarketPriceHelper $marketPriceHelper)
{
$this->priceHelper = $marketPriceHelper;
}
public function parseCollection(BaseCollection &$collection)
{
$key = $collection->getUniqueKey();
if (isset(self::$collectionCache[$key]) && !empty(self::$collectionCache[$key]))
return self::$collectionCache[$key];
if ($collection->isEmpty())
return [];
/** @var BaseCollectionFilter $filter */
foreach ($collection->getFilters() as $filter) {
$filter->execute($collection);
}
$result = [];
foreach ($collection->toArray() as $itemInfo) {
$item = $this->parseItem($itemInfo);
$result[] = $item;
}
self::$collectionCache[$key] = $result;
return $result;
}
/**
* @param $itemInfo
* @return Item
*/
public function parseItem($itemInfo)
{
if (empty($itemInfo) || !is_array($itemInfo))
throw new \InvalidArgumentException('ItemInfo should be filled array!');
$key = md5(json_encode($itemInfo));
if (isset(self::$itemCache[$key]) && !empty(self::$itemCache[$key]))
return self::$itemCache[$key];
/** @var Item $item */
$item = ItemStorage::createItem($itemInfo);
if ($itemInfo['tradable'] !== '0') {
try {
/** @var $price ItemPrice */
$price = $this->priceHelper->getItemPrice($item);
$item->setPrice($price);
} catch (\Exception $e) {
\Log::debug($e->getMessage());
}
}
return $item;
}
}
<?php
namespace App\Helpers\Steam;
use App\Helpers\Steam\Items\Item;
use App\Models\ItemPrice;
use Cache;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ItemMarketPriceHelper
{
// protected $communityURL = 'http://anonymouse.org/cgi-bin/anon-www.cgi/http://steamcommunity.com/';
protected $communityURL = 'http://steamcommunity.com/';
protected $apiKey;
/**
* @var int
*/
protected $currency;
/**
* @param $apiKey
* @param int $currency
*/
public function __construct($apiKey, $currency = 5)
{
$this->apiKey = $apiKey;
$this->currency = $currency;
}
protected function cleanFloat($str)
{
$float = (float)str_replace(',', '.', preg_replace('/[\s]*/', '', $str));
return $float;
}
protected function cleanInt($str)
{
return (int)str_replace(',', '.', preg_replace('/[\s]*/', '', $str));
}
public function setCurrency($currency)
{
$this->currency = $currency;
}
/**
* @param $item Item
* @return ItemPrice
*/
public function getItemPrice($item)
{
$params = [
'currency' => $this->currency,
'appid' => $item->getAppId(),
'market_hash_name' => $item->getMarketName()
];
$query = http_build_query($params);
$price = ItemPrice::
where('class_id', $item->getClassId())
->where('app_id', $item->getAppId())
->first();
if (!$price) {
$url = $this->communityURL . 'market/priceoverview/?' . $query;
$contents = @file_get_contents($url);
$result = json_decode($contents, true);
if (!$result['success']) {
throw new NotFoundHttpException('Стим считает нас дудосерами по ссылке ' . $url);
}
$lowest = (isset($result['lowest_price'])) ? $this->cleanFloat($result['lowest_price']) : 0;
$median = (isset($result['median_price'])) ? $this->cleanFloat($result['median_price']) : 0;
$volume = (isset($result['volume'])) ? $this->cleanInt($result['volume']) : 0;
$cached = [
'class_id' => $item->getClassId(),
'app_id' => $item->getAppId(),
'min' => $lowest,
'median' => $median,
'volume' => $volume,
];
$price = ItemPrice::create($cached);
}
return $price;
}
}
<?php
class ItemStorage
{
private static $relations = [
'570' => DotaItem::class,
];
public static function getItemClass($appId)
{
$appId = $appId . '';
if (!isset(self::$relations[$appId])) {
throw new \InvalidArgumentException('Theres no item for id: ' . $appId);
}
return self::$relations[$appId];
}
public static function getItemContextID($appId)
{
$className = self::getItemClass($appId);
$prop = 'contextId';
if (!property_exists($className, $prop))
throw new \RuntimeException('Item called ' . $className . ' hasnt ContextID! Fix it!');
$vars = get_class_vars($className);
return $vars[$prop];
}
/**
* @param $itemInfo
* @return mixed
*/
public static function createItem($itemInfo)
{
$item = self::getItemClass($itemInfo['appid']);
/* @var $item Item */
return new $item($itemInfo);
}
}
<?php
class SliceCollectionFilter implements BaseCollectionFilter
{
/**
* @var int
*/
private $from;
/**
* @var int
*/
private $to;
/**
* SliceCollectionFilter constructor.
* @param int $from
* @param int $to
*/
public function __construct($from, $to)
{
$this->from = max(0, $from);
$this->to = (int)$to;
}
public function execute(BaseCollection &$collection)
{
$items = $collection->toArray();
$bad = [];
foreach ($items as $i => $v) {
if ($i < $this->from || $i >= $this->to) {
$bad[] = $i;
}
}
$collection->removeAtMany($bad);
}
public function __toString()
{
return 'Slice';
}
}
<?php
class WhereCollectionFilter implements BaseCollectionFilter
{
/**
* @var \Closure
*/
private $closure;
/**
* WhereCollectionFilter constructor.
* @param \Closure $closure
*/
public function __construct(\Closure &$closure)
{
$this->closure = &$closure;
}
public function execute(BaseCollection &$collection)
{
$bad = [];
/* @var $closure \Closure */
$closure = $this->closure;
$items = $collection->toArray();
foreach ($items as $i => $v) {
if (!$closure($v)) {
$bad[] = $i;
}
}
$collection->removeAtMany($bad);
}
public function __toString()
{
return 'Where';
}
}
@iamcsharper
Copy link
Author

Will be waiting for advices and fixed!)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment