Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ typings/

.idea

debug/config.json

116 changes: 76 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,80 @@
#!!!

#Not fully refactored - some parts will be developed or rewritten with the bots modules - according to their actual requirements


The main focus of this work is to structure emitters chain,
to dedicate the responsibility and to reduce coupling, codebase size,
to make it able to be customized, to implement DRY etc

# CS:GO-market Manager
High level wrapper for market.csgo.com (AKA tm.csgo.com)
> Only items bought supported currently and focuses on CS:GO

## Config

### Minimal required

```json
{
"market": {
"apiKey": "xxx"
}
}
```

### Default values

```json5
{
"manager": {
"balanceValidationInterval": 90000, // 1.5 * 60 * 1000 - Balance integrity check if we are connected to ws
"avoidBadBots": true, // If we know that some offer is laggy we will firstly try to skip it
"safeBuyRequests": true, // If market returns http error on by request we will check, did we really bought
"dataDir": null, // Where system data should be stored, should be absolute path or nothing
"logApiCalls": false // Should we log all api calls to market?(works only if data dir is set) . Or you can pass your own logger
},
"market": {
"apiKey": "", // Required
"pingInterval": 185000, // 3 * 60 * 1000 + 5 * 1000
"handleTimezone": false,
"allowedPriceFluctuation": 0,
"compromiseFactor": 0,
"minCompromise": 0
},
"sockets": {
"pingInterval": 20000 // 20 * 1000
},
"knapsack": {
"validationInterval": 60000, // 60 * 1000 - if we have connection to ws
"updateInterval": 20000 // 20 * 1000 - using if don't have connection to ws
}
}
```
##Import
``const MarketManager = require('market-csgo-manager')
``

##Usage
````javascript
const marketManager = new MarketManager({
APIKey: 'xxxrrr44'
})
````
Then subscribe to events list (to be fullfilled)

````javascript
marketManager.on('start', () => {})
marketManager.on('itemBought', item => {})
marketManager.on('wsStuck', () => {})
marketManager.on('APITimeout', () => {})


````

##Structure
General class is dist/MarketManager

it takes API key as an argument
Then it inits APIProvider lib, starts reconnecting WS server and
subscribes to WS events, also it inits cache manager etc and binds
methods

class methods are in versions/v2

each method has a directory with index file and helpers

like versions/v2/buy
- index.js
- helpers
- - get_item_ids.js

libs keeping offers cache and balance data are in ./lib

WS stuff is in ./lib/ws

WebSocket manager is a class decorating WS client class,
it decides what to do if connection fails, triggers different callbacks
on messages after parse and type checking etc

All libs can trigger events on main class which causes either
some class methods calls or emitting events to which
user app is subscribed


# Build

`npm run build` compiles stuff from `src` dir to `dist`

# Tests

1) in `test/stuff` rename `.test_API_key` to `test_API_key`
2) add your key there
3) run `npm run test`. It compiles .ts to .js and tests js out

# N.B.:

As an Orthodox Russian redneck, I suffer from 'shaitan' naming >:-)

Hope this lib will work `Ad majore Dei gloriam`

5 changes: 5 additions & 0 deletions config/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"connection_timeout": 2000,
"reconnection_delay_grow_factor": 2,
"max_retries": 8
}
67 changes: 67 additions & 0 deletions dist/MarketManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// @ts-ignore
const MarketAPIProvider = require('src/helpers/get_market_API');
// @ts-ignore
const BadOffersCache = require('./lib/cache');
// @ts-ignore
const MarketWS = require('./lib/ws');
// @ts-ignore
const EventEmitter = require('events');
// @ts-ignore
const MarketKnapsack = require('./lib/knapsack');
const v1 = require('./versions/v1');
const v2 = require('./versions/v2');
const generalEmitter = require('./emitters');
module.exports = class MarketManager extends EventEmitter {
constructor(initOptions) {
super();
/**
* Set API key
* @type {string}
*/
this.APIKey = initOptions.APIKey;
/**
* Set default currency
* @type {"USD" | "RUB" | "EUR"}
*/
this.currency = initOptions.currency || this.currency;
/**
* Set API version
* @type {string}
*/
this.version = initOptions.version || this.version;
/**
*
* Hold all event emitters in one module
*/
this.emitEvent = generalEmitter.bind(this);
/**
* Init API provider lib locally or from repository WS client to get data from CSGO market
*/
this.marketAPIProvider = new MarketAPIProvider({
APIKey: initOptions.APIKey
});
/**
* Init Node cache for bad offers
* @type {BadOffersCache}
*/
this.badOffersCache = new BadOffersCache({});
this.knapsack = new MarketKnapsack({});
/**
* Init WS client to get data from CSGO market with `this` passed
*/
this.marketWS = new MarketWS({
marketAPIProvider: this.marketAPIProvider,
version: this.version
});
/**
* Create getter v1 to be called like Manager.v1.buy()
* Not ready now for v2 has higher priority
*/
Object.defineProperty(this, 'v1', { get: v1.bind(this) });
/**
* Create getter v2 to be called like Manager.v2.buy()
*/
Object.defineProperty(this, 'v2', { get: v2.bind(this) });
this[this.version].setWSEvents.call(this);
}
};
67 changes: 21 additions & 46 deletions lib/BadOffersCache.js → dist/_lib/BadOffersCache.js
Original file line number Diff line number Diff line change
@@ -1,126 +1,101 @@
"use strict";

const NodeCache = require("node-cache");

const globalCommonCounter = {};
const globalPreciseCounter = {};

module.exports = BadOffersCache;

/**
* @param {CBadOffersConfig} config
* @constructor
*/
function BadOffersCache(config) {
this._config = config;

this._commonCounters = this._config.shareCounters ? globalCommonCounter : {};
this._preciseCounters = this._config.shareCounters ? globalPreciseCounter : {};

// initially we did not need to have such cache
// but due to not working knapsack module we are force to use it for now
this._offersCache = new NodeCache({
stdTTL: this._config.boughtOffersCache,
});

this.started = false;
}

BadOffersCache.prototype.start = function() {
if(this.started) {
BadOffersCache.prototype.start = function () {
if (this.started) {
return;
}
this.started = true;

setInterval(() => {
this._autoDecrease(this._commonCounters);
this._autoDecrease(this._preciseCounters);
}, this._config.updateInterval);
};

BadOffersCache.prototype.markAsBad = function(item) {
BadOffersCache.prototype.markAsBad = function (item) {
let commonHash = this._getCommonHashId(item);
this._updateCounter(this._commonCounters, commonHash);

if(typeof item.price !== "undefined") {
if (typeof item.price !== "undefined") {
let preciseHash = this._getPreciseHashId(item);
this._updateCounter(this._preciseCounters, preciseHash);
}
};

/**
* "Temporary" method. You may use it from your client application
*/
BadOffersCache.prototype.markAsBadByUid = function(uid, fallback = null) {
BadOffersCache.prototype.markAsBadByUid = function (uid, fallback = null) {
let item = this._findItemByUid(uid);
if(item) {
if (item) {
fallback = item;
}

if(fallback) {
if (fallback) {
this.markAsBad(fallback);
}
};

BadOffersCache.prototype.storeBoughtOffer = function(boughtItem) {
BadOffersCache.prototype.storeBoughtOffer = function (boughtItem) {
this._offersCache.set(String(boughtItem.uiId), {
instanceId: boughtItem.instanceId,
classId: boughtItem.classId,
price: boughtItem.offerPrice,
});
};

BadOffersCache.prototype.isBad = function(item) {
BadOffersCache.prototype.isBad = function (item) {
let commonHash = this._getCommonHashId(item);
let preciseHash = this._getPreciseHashId(item);

if(this._commonCounters[commonHash] && this._commonCounters[commonHash].fails >= this._config.minCommonFails) {
if (this._commonCounters[commonHash] && this._commonCounters[commonHash].fails >= this._config.minCommonFails) {
return true;
}
if(this._preciseCounters[preciseHash] && this._preciseCounters[preciseHash].fails >= this._config.minPreciseFails) {
if (this._preciseCounters[preciseHash] && this._preciseCounters[preciseHash].fails >= this._config.minPreciseFails) {
return true;
}

return false;
};

BadOffersCache.prototype._findItemByUid = function(uid) {
BadOffersCache.prototype._findItemByUid = function (uid) {
return this._offersCache.get(String(uid));
};

BadOffersCache.prototype._updateCounter = function(counter, hash) {
if(!counter[hash]) {
BadOffersCache.prototype._updateCounter = function (counter, hash) {
if (!counter[hash]) {
counter[hash] = {
lastUpdate: Number.MAX_VALUE,
fails: 0,
};
}

counter[hash].lastUpdate = Date.now();
counter[hash].fails += 1;
};

BadOffersCache.prototype._autoDecrease = function(counter) {
for(let hashid in counter) {
if(counter.hasOwnProperty(hashid)) {
BadOffersCache.prototype._autoDecrease = function (counter) {
for (let hashid in counter) {
if (counter.hasOwnProperty(hashid)) {
let timePassed = Date.now() - counter[hashid].lastUpdate;

if(timePassed > this._config.penaltyTime) {
if (timePassed > this._config.penaltyTime) {
counter[hashid].lastUpdate = Date.now();
counter[hashid].fails -= 1;

if(counter[hashid].fails <= 0) {
if (counter[hashid].fails <= 0) {
delete counter[hashid];
}
}
}
}
};

BadOffersCache.prototype._getCommonHashId = function(item) {
BadOffersCache.prototype._getCommonHashId = function (item) {
return item.instanceId + "_" + item.classId;
};

BadOffersCache.prototype._getPreciseHashId = function(item) {
BadOffersCache.prototype._getPreciseHashId = function (item) {
return this._getCommonHashId(item) + "_" + item.price;
};
Loading