<commit_before>'use strict';

// ---------------------------------------------------------------------------

const acx = require ('./acx.js');
const { ExchangeError } = require ('./base/errors');

// ---------------------------------------------------------------------------

module.exports = class kuna extends acx {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'kuna',
            'name': 'Kuna',
            'countries': 'UA',
            'rateLimit': 1000,
            'version': 'v2',
            'has': {
                'CORS': false,
                'fetchTickers': true,
                'fetchOpenOrders': true,
                'fetchMyTrades': true,
                'withdraw': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/31697638-912824fa-b3c1-11e7-8c36-cf9606eb94ac.jpg',
                'api': 'https://kuna.io',
                'www': 'https://kuna.io',
                'doc': 'https://kuna.io/documents/api',
                'fees': 'https://kuna.io/documents/api',
            },
            'api': {
                'public': {
                    'get': [
                        'tickers', // all of them at once
                        'tickers/{market}',
                        'order_book',
                        'order_book/{market}',
                        'trades',
                        'trades/{market}',
                        'timestamp',
                    ],
                },
                'private': {
                    'get': [
                        'members/me',
                        'orders',
                        'trades/my',
                    ],
                    'post': [
                        'orders',
                        'order/delete',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.25 / 100,
                    'maker': 0.25 / 100,
                },
                'funding': {
                    'withdraw': {
                        'UAH': '1%',
                        'BTC': 0.001,
                        'BCH': 0.001,
                        'ETH': 0.01,
                        'WAVES': 0.01,
                        'GOL': 0.0,
                        'GBG': 0.0,
                        // 'RMC': 0.001 BTC
                        // 'ARN': 0.01 ETH
                        // 'R': 0.01 ETH
                        // 'EVR': 0.01 ETH
                    },
                    'deposit': {
                        // 'UAH': (amount) => amount * 0.001 + 5
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let predefinedMarkets = [
            { 'id': 'btcuah', 'symbol': 'BTC/UAH', 'base': 'BTC', 'quote': 'UAH', 'baseId': 'btc', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'ethuah', 'symbol': 'ETH/UAH', 'base': 'ETH', 'quote': 'UAH', 'baseId': 'eth', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'gbguah', 'symbol': 'GBG/UAH', 'base': 'GBG', 'quote': 'UAH', 'baseId': 'gbg', 'quoteId': 'uah', 'precision': { 'amount': 3, 'price': 2 }, 'lot': 0.001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 0.01, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}}, // Golos Gold (GBG != GOLOS)
            { 'id': 'kunbtc', 'symbol': 'KUN/BTC', 'base': 'KUN', 'quote': 'BTC', 'baseId': 'kun', 'quoteId': 'btc', 'precision': { 'amount': 6, 'price': 6 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 0.000001, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'bchbtc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bch', 'quoteId': 'btc', 'precision': { 'amount': 6, 'price': 6 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 0.000001, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'bchuah', 'symbol': 'BCH/UAH', 'base': 'BCH', 'quote': 'UAH', 'baseId': 'bch', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'wavesuah', 'symbol': 'WAVES/UAH', 'base': 'WAVES', 'quote': 'UAH', 'baseId': 'waves', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'arnbtc', 'symbol': 'ARN/BTC', 'base': 'ARN', 'quote': 'BTC', 'baseId': 'arn', 'quoteId': 'btc' },
            { 'id': 'b2bbtc', 'symbol': 'B2B/BTC', 'base': 'B2B', 'quote': 'BTC', 'baseId': 'b2b', 'quoteId': 'btc' },
            { 'id': 'evrbtc', 'symbol': 'EVR/BTC', 'base': 'EVR', 'quote': 'BTC', 'baseId': 'evr', 'quoteId': 'btc' },
            { 'id': 'golgbg', 'symbol': 'GOL/GBG', 'base': 'GOL', 'quote': 'GBG', 'baseId': 'gol', 'quoteId': 'gbg' },
            { 'id': 'rbtc', 'symbol': 'R/BTC', 'base': 'R', 'quote': 'BTC', 'baseId': 'r', 'quoteId': 'btc' },
            { 'id': 'rmcbtc', 'symbol': 'RMC/BTC', 'base': 'RMC', 'quote': 'BTC', 'baseId': 'rmc', 'quoteId': 'btc' },
        ];
        let markets = [];
        let tickers = await this.publicGetTickers ();
        for (let i = 0; i < predefinedMarkets.length; i++) {
            let market = predefinedMarkets[i];
            if (market['id'] in tickers)
                markets.push (market);
        }
        let marketsById = this.indexBy (markets, 'id');
        let ids = Object.keys (tickers);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            if (!(id in marketsById)) {
                let baseId = id.replace ('btc', '');
                baseId = baseId.replace ('uah', '');
                baseId = baseId.replace ('gbg', '');
                if (baseId.length > 0) {
                    let baseIdLength = baseId.length - 0; // a transpiler workaround
                    let quoteId = id.slice (baseIdLength);
                    let base = baseId.toUpperCase ();
                    let quote = quoteId.toUpperCase ();
                    base = this.commonCurrencyCode (base);
                    quote = this.commonCurrencyCode (quote);
                    let symbol = base + '/' + quote;
                    markets.push ({
                        'id': id,
                        'symbol': symbol,
                        'base': base,
                        'quote': quote,
                        'baseId': baseId,
                        'quoteId': quoteId,
                    });
                }
            }
        }
        return markets;
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        let market = this.market (symbol);
        let orderBook = await this.publicGetOrderBook (this.extend ({
            'market': market['id'],
        }, params));
        return this.parseOrderBook (orderBook, undefined, 'bids', 'asks', 'price', 'remaining_volume');
    }

    async fetchL3OrderBook (symbol, limit = undefined, params = {}) {
        return this.fetchOrderBook (symbol, limit, params);
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (!symbol)
            throw new ExchangeError (this.id + ' fetchOpenOrders requires a symbol argument');
        let market = this.market (symbol);
        let orders = await this.privateGetOrders (this.extend ({
            'market': market['id'],
        }, params));
        // todo emulation of fetchClosedOrders, fetchOrders, fetchOrder
        // with order cache + fetchOpenOrders
        // as in BTC-e, Liqui, Yobit, DSX, Tidex, WEX
        return this.parseOrders (orders, market, since, limit);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = this.parse8601 (trade['created_at']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'id': trade['id'].toString (),
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'type': undefined,
            'side': undefined,
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['volume']),
            'info': trade,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        let market = this.market (symbol);
        let response = await this.publicGetTrades (this.extend ({
            'market': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    parseMyTrade (trade, market) {
        let timestamp = this.parse8601 (trade['created_at']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'id': trade['id'],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'price': trade['price'],
            'amount': trade['volume'],
            'cost': trade['funds'],
            'symbol': symbol,
            'side': trade['side'],
            'order': trade['order_id'],
        };
    }

    parseMyTrades (trades, market = undefined) {
        let parsedTrades = [];
        for (let i = 0; i < trades.length; i++) {
            let trade = trades[i];
            let parsedTrade = this.parseMyTrade (trade, market);
            parsedTrades.push (parsedTrade);
        }
        return parsedTrades;
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (!symbol)
            throw new ExchangeError (this.id + ' fetchOpenOrders requires a symbol argument');
        let market = this.market (symbol);
        let response = await this.privateGetTradesMy ({ 'market': market['id'] });
        return this.parseMyTrades (response, market);
    }
};
<commit_msg>kuna: await this.loadMarkets ()<commit_after>'use strict';

// ---------------------------------------------------------------------------

const acx = require ('./acx.js');
const { ExchangeError } = require ('./base/errors');

// ---------------------------------------------------------------------------

module.exports = class kuna extends acx {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'kuna',
            'name': 'Kuna',
            'countries': 'UA',
            'rateLimit': 1000,
            'version': 'v2',
            'has': {
                'CORS': false,
                'fetchTickers': true,
                'fetchOpenOrders': true,
                'fetchMyTrades': true,
                'withdraw': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/31697638-912824fa-b3c1-11e7-8c36-cf9606eb94ac.jpg',
                'api': 'https://kuna.io',
                'www': 'https://kuna.io',
                'doc': 'https://kuna.io/documents/api',
                'fees': 'https://kuna.io/documents/api',
            },
            'api': {
                'public': {
                    'get': [
                        'tickers', // all of them at once
                        'tickers/{market}',
                        'order_book',
                        'order_book/{market}',
                        'trades',
                        'trades/{market}',
                        'timestamp',
                    ],
                },
                'private': {
                    'get': [
                        'members/me',
                        'orders',
                        'trades/my',
                    ],
                    'post': [
                        'orders',
                        'order/delete',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.25 / 100,
                    'maker': 0.25 / 100,
                },
                'funding': {
                    'withdraw': {
                        'UAH': '1%',
                        'BTC': 0.001,
                        'BCH': 0.001,
                        'ETH': 0.01,
                        'WAVES': 0.01,
                        'GOL': 0.0,
                        'GBG': 0.0,
                        // 'RMC': 0.001 BTC
                        // 'ARN': 0.01 ETH
                        // 'R': 0.01 ETH
                        // 'EVR': 0.01 ETH
                    },
                    'deposit': {
                        // 'UAH': (amount) => amount * 0.001 + 5
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let predefinedMarkets = [
            { 'id': 'btcuah', 'symbol': 'BTC/UAH', 'base': 'BTC', 'quote': 'UAH', 'baseId': 'btc', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'ethuah', 'symbol': 'ETH/UAH', 'base': 'ETH', 'quote': 'UAH', 'baseId': 'eth', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'gbguah', 'symbol': 'GBG/UAH', 'base': 'GBG', 'quote': 'UAH', 'baseId': 'gbg', 'quoteId': 'uah', 'precision': { 'amount': 3, 'price': 2 }, 'lot': 0.001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 0.01, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}}, // Golos Gold (GBG != GOLOS)
            { 'id': 'kunbtc', 'symbol': 'KUN/BTC', 'base': 'KUN', 'quote': 'BTC', 'baseId': 'kun', 'quoteId': 'btc', 'precision': { 'amount': 6, 'price': 6 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 0.000001, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'bchbtc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bch', 'quoteId': 'btc', 'precision': { 'amount': 6, 'price': 6 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 0.000001, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'bchuah', 'symbol': 'BCH/UAH', 'base': 'BCH', 'quote': 'UAH', 'baseId': 'bch', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'wavesuah', 'symbol': 'WAVES/UAH', 'base': 'WAVES', 'quote': 'UAH', 'baseId': 'waves', 'quoteId': 'uah', 'precision': { 'amount': 6, 'price': 0 }, 'lot': 0.000001, 'limits': { 'amount': { 'min': 0.000001, 'max': undefined }, 'price': { 'min': 1, 'max': undefined }, 'cost': { 'min': 0.000001, 'max': undefined }}},
            { 'id': 'arnbtc', 'symbol': 'ARN/BTC', 'base': 'ARN', 'quote': 'BTC', 'baseId': 'arn', 'quoteId': 'btc' },
            { 'id': 'b2bbtc', 'symbol': 'B2B/BTC', 'base': 'B2B', 'quote': 'BTC', 'baseId': 'b2b', 'quoteId': 'btc' },
            { 'id': 'evrbtc', 'symbol': 'EVR/BTC', 'base': 'EVR', 'quote': 'BTC', 'baseId': 'evr', 'quoteId': 'btc' },
            { 'id': 'golgbg', 'symbol': 'GOL/GBG', 'base': 'GOL', 'quote': 'GBG', 'baseId': 'gol', 'quoteId': 'gbg' },
            { 'id': 'rbtc', 'symbol': 'R/BTC', 'base': 'R', 'quote': 'BTC', 'baseId': 'r', 'quoteId': 'btc' },
            { 'id': 'rmcbtc', 'symbol': 'RMC/BTC', 'base': 'RMC', 'quote': 'BTC', 'baseId': 'rmc', 'quoteId': 'btc' },
        ];
        let markets = [];
        let tickers = await this.publicGetTickers ();
        for (let i = 0; i < predefinedMarkets.length; i++) {
            let market = predefinedMarkets[i];
            if (market['id'] in tickers)
                markets.push (market);
        }
        let marketsById = this.indexBy (markets, 'id');
        let ids = Object.keys (tickers);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            if (!(id in marketsById)) {
                let baseId = id.replace ('btc', '');
                baseId = baseId.replace ('uah', '');
                baseId = baseId.replace ('gbg', '');
                if (baseId.length > 0) {
                    let baseIdLength = baseId.length - 0; // a transpiler workaround
                    let quoteId = id.slice (baseIdLength);
                    let base = baseId.toUpperCase ();
                    let quote = quoteId.toUpperCase ();
                    base = this.commonCurrencyCode (base);
                    quote = this.commonCurrencyCode (quote);
                    let symbol = base + '/' + quote;
                    markets.push ({
                        'id': id,
                        'symbol': symbol,
                        'base': base,
                        'quote': quote,
                        'baseId': baseId,
                        'quoteId': quoteId,
                    });
                }
            }
        }
        return markets;
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let orderBook = await this.publicGetOrderBook (this.extend ({
            'market': market['id'],
        }, params));
        return this.parseOrderBook (orderBook, undefined, 'bids', 'asks', 'price', 'remaining_volume');
    }

    async fetchL3OrderBook (symbol, limit = undefined, params = {}) {
        return this.fetchOrderBook (symbol, limit, params);
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (!symbol)
            throw new ExchangeError (this.id + ' fetchOpenOrders requires a symbol argument');
        await this.loadMarkets ();
        let market = this.market (symbol);
        let orders = await this.privateGetOrders (this.extend ({
            'market': market['id'],
        }, params));
        // todo emulation of fetchClosedOrders, fetchOrders, fetchOrder
        // with order cache + fetchOpenOrders
        // as in BTC-e, Liqui, Yobit, DSX, Tidex, WEX
        return this.parseOrders (orders, market, since, limit);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = this.parse8601 (trade['created_at']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'id': trade['id'].toString (),
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'type': undefined,
            'side': undefined,
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['volume']),
            'info': trade,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTrades (this.extend ({
            'market': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    parseMyTrade (trade, market) {
        let timestamp = this.parse8601 (trade['created_at']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'id': trade['id'],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'price': trade['price'],
            'amount': trade['volume'],
            'cost': trade['funds'],
            'symbol': symbol,
            'side': trade['side'],
            'order': trade['order_id'],
        };
    }

    parseMyTrades (trades, market = undefined) {
        let parsedTrades = [];
        for (let i = 0; i < trades.length; i++) {
            let trade = trades[i];
            let parsedTrade = this.parseMyTrade (trade, market);
            parsedTrades.push (parsedTrade);
        }
        return parsedTrades;
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (!symbol)
            throw new ExchangeError (this.id + ' fetchOpenOrders requires a symbol argument');
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.privateGetTradesMy ({ 'market': market['id'] });
        return this.parseMyTrades (response, market);
    }
};
<|endoftext|>


<commit_before>/*
==================================================================================================
  Jasy - JavaScript Tooling Framework
  Copyright 2010-2011 Sebastian Werner
--------------------------------------------------------------------------------------------------
  Inspired by: https://github.com/inexorabletash/raf-shim/blob/master/raf.js
==================================================================================================
*/

/**
 * @require {fix.ObjectKeys}
 * @require {fix.DateParse}
 */
(function(global)
{
	if(global.requestAnimationFrame) {
		return;
	}

	// requestAnimationFrame polyfill
	// http://webstuff.nfshost.com/anim-timing/Overview.html

	var postfix = "RequestAnimationFrame";
	var prefix = (function()
	{
		var all = "webkit,moz,o,ms".split(",");
		for (var i=0; i<4; i++) {
			if (global[all[i]+postfix] != null) {
				return all[i];
			}
		}
	})();

	// Vendor specific implementation
	if (prefix)
	{
		global.requestAnimationFrame = global[prefix+postfix];
		global.cancelRequestAnimationFrame = global[prefix+"Cancel"+postfix];
		return;
	}

	// Custom implementation
	var TARGET_FPS = 60;
	var requests = {};
	var rafHandle = 1;
	var timeoutHandle = null;

	global.requestAnimationFrame = function requestAnimationFrame(callback)
	{
		var callbackHandle = rafHandle++;

		// Store callback
		requests[callbackHandle] = callback;

		// Create timeout at first request
		if (timeoutHandle === null)
		{
			timeoutHandle = setTimeout(function()
			{
				var time = Date.now();
				var currentRequests = requests;
				var keys = Object.keys(currentRequests);

				// Reset data structure before executing callbacks
				requests = {};
				timeoutHandle = null;

				// Process all callbacks
				for (var i=0, l=keys.length; i<l; i++) {
					currentRequests[keys[i]](time);
				}
			}, 1000 / TARGET_FPS);
		}

		return callbackHandle;
	};

	global.cancelRequestAnimationFrame = function cancelRequestAnimationFrame(handle)
	{
		delete requests[handle];

		// Stop timeout if all where removed
		if (Object.empty(requests))
		{
			clearTimeout(timeoutHandle);
			timeoutHandle = null;
		}
	};

})(this);<commit_msg>Added root to emulation<commit_after>/*
==================================================================================================
  Jasy - JavaScript Tooling Framework
  Copyright 2010-2011 Sebastian Werner
--------------------------------------------------------------------------------------------------
  Inspired by: https://github.com/inexorabletash/raf-shim/blob/master/raf.js
==================================================================================================
*/

/**
 * @require {fix.ObjectKeys}
 * @require {fix.DateParse}
 */
(function(global)
{
	if(global.requestAnimationFrame) {
		return;
	}

	// requestAnimationFrame polyfill
	// http://webstuff.nfshost.com/anim-timing/Overview.html

	var postfix = "RequestAnimationFrame";
	var prefix = (function()
	{
		var all = "webkit,moz,o,ms".split(",");
		for (var i=0; i<4; i++) {
			if (global[all[i]+postfix] != null) {
				return all[i];
			}
		}
	})();

	// Vendor specific implementation
	if (prefix)
	{
		global.requestAnimationFrame = global[prefix+postfix];
		global.cancelRequestAnimationFrame = global[prefix+"Cancel"+postfix];
		return;
	}

	// Custom implementation
	var TARGET_FPS = 60;
	var requests = {};
	var rafHandle = 1;
	var timeoutHandle = null;

	global.requestAnimationFrame = function(callback, root)
	{
		var callbackHandle = rafHandle++;

		// Store callback
		requests[callbackHandle] = callback;

		// Create timeout at first request
		if (timeoutHandle === null)
		{
			timeoutHandle = setTimeout(function()
			{
				var time = Date.now();
				var currentRequests = requests;
				var keys = Object.keys(currentRequests);

				// Reset data structure before executing callbacks
				requests = {};
				timeoutHandle = null;

				// Process all callbacks
				for (var i=0, l=keys.length; i<l; i++) {
					currentRequests[keys[i]](time);
				}
			}, 1000 / TARGET_FPS);
		}

		return callbackHandle;
	};

	global.cancelRequestAnimationFrame = function cancelRequestAnimationFrame(handle)
	{
		delete requests[handle];

		// Stop timeout if all where removed
		if (Object.empty(requests))
		{
			clearTimeout(timeoutHandle);
			timeoutHandle = null;
		}
	};

})(this);<|endoftext|>


<commit_before>#!/usr/bin/env node
/*jslint node:true, indent:2, regexp:true, nomen:true */
"use strict";

var yargs = require("yargs")
    .usage("Rename downloaded TV episode files.\n $0 [options] [file]")
    .example("$0 community-s01e04.avi", "rename file to Community - S01E04 - Social Psychology.avi")
    .example("$0 --offset=-1 community-s01e04.avi", "rename file to Community - S01E03 - Introduction to Film.avi")
    .example("$0 -S Community -s 1 -e 2 unknown.avi", "rename file to Community - S01E02 - Spanish 101.avi")
    .describe("v", "Display version number")
    .alias("v", "version")
    .describe("h", "Display help")
    .alias("h", "help")
    .describe("S", "Stipulate showname")
    .alias("S", "show")
    .describe("s", "Stipulate season number")
    .alias("s", "season")
    .describe("e", "Stipulate episode number")
    .alias("e", "episode")
    .describe("o", "Offset episode number")
    .alias("o", "offset")
    .describe("f", "Force overwrite of existing files.")
    .alias("f", "force")
    .describe("save_api_key", "Saves your trakt api key"),
  argv = yargs.argv,
  episoder = argv.$0,
  trakt,
  path = require("path"),
  configPath = path.join(__dirname, "..", "config", "traktApiKey.js"),
  fs = require("fs"),
  async = require("async"),
  titleCase = require("to-title-case"),
  errCouldNotRename = "unable to rename ",
  zPad = require("./helpers.js").zPad,
  sanitize = require("sanitize-filename"),
  packageJson = require("../package.json");

if (argv.save_api_key) {
  // using synchronous method, because nothing can happen until this has finished:
  try {
    /*jslint stupid: true*/
    fs.writeFileSync(configPath, "module.exports = '" + argv.save_api_key + "';\n");
    /*jslint stupid: false*/
  } catch (e) {
    if (e.code === "EACCES") {
      console.log("Permission denied. You probably need to be root to write this file.");
      process.exit(1);
    }
    console.log("There was an unexpected error writing your file:", JSON.stringify(e));
    process.exit(1);
  }
  console.log("Your api key has been written to file.");
  process.exit(0);
} else {
  trakt = require("./trakt");
}

// filename: string
// options: object
function parseFilename(filename, options) {
  var ext = path.extname(filename).toLowerCase(),
  // the following regex should match:
  //   Community S01E04.mp4
  //   Community s01e04.mp4
  //   Community 1x04.mp4
  //   Community 1-04.mp4
    re = /(.*)\D(\d{1,2})[ex\-](\d{1,2})/i,
    searchResults = filename.match(re),
    show,
    season,
    episode,
    offset,
    episodeObject = {};

  if (options === undefined) {
    options = {};
  }

  offset = options.offset || 0;
  if (searchResults === null) {
    // this regex should match:
    //   Community Season 1 Episode 4.mp4
    // (case insensitive)
    re = /(.*)Season.*?(\d{1,2}).*Episode\D*?(\d{1,2})/i;
    searchResults = filename.match(re);
  }

  if (searchResults === null) {
    // this regex should match:
    //   Community 104.mp4
    re = /(.*)\D(\d)(\d\d)\D/;
    searchResults = filename.match(re);
  }

  if (searchResults === null && options.season) {
    // this regex should match:
    //   Community 04.mp4
    // but only if we've specified a season with season flag
    re = /(.*)\D(\d+)\D/;
    searchResults = filename.match(re);
  }

  if (searchResults === null && options.season && options.show) {
    // this regex should match:
    //   04.mp4
    // but only if we've specified a season and show with flags
    re = /(\d*)\D/;
    searchResults = filename.match(re);
  }

  try {
    show = options.show || searchResults[1];
  } catch (e) {
    return null;
  }
  show = titleCase(show
      // remove hanging characters
      .replace(/^[\-.\s]+|[\-.\s]+$/g, "")
      .trim());

  if (options.episode) {
    episode = options.episode + offset;
    if (searchResults !== null) {
      searchResults.pop();
    }
  } else {
    try {
      episode = Number(searchResults.pop()) + offset;
    } catch (e) {
      return null;
    }
  }

  season = options.season || Number(searchResults.pop());

  episodeObject = {
    originalFilename: filename,
    show: show,
    season: season,
    episode: episode,
    extension: ext
  };
  return episodeObject;
}

// filename: string
// options: object
function getReplacementFilename(filename, options, callback) {
  var episodeObject = parseFilename(filename, options);

  if (episodeObject === null) {
    return callback(errCouldNotRename + filename);
  }

  trakt.getTitle(episodeObject, function (err, episodeObject) {
    if (err) {
      callback(err);
      return;
    }
    var show = episodeObject.show,
      season = zPad(episodeObject.season),
      episode = "E" + zPad(episodeObject.episode),
      title = episodeObject.title,
      ext = episodeObject.extension,
      replacementFilename;

    if (isNaN(Number(season))) {
      season = titleCase(season) + " ";
    } else {
      season = "S" + season;
    }
    replacementFilename = sanitize(show + " - " + season + episode + " - " + title +  ext);

    callback(null, replacementFilename);
  });
}

// src: filepath string
// options: object
function renameEpisodeFile(src, options, callback) {
  var filename = path.basename(src),
    dir = path.dirname(src);

  getReplacementFilename(filename, options, function (err, replacementFilename) {
    if (err) {
      console.error(err);
      return;
    }

    var dest = path.join(dir, replacementFilename);

    fs.stat(dest, function (err) {
      if ((err && err.code === "ENOENT") || (options && options.force)) {
        fs.rename(src, dest);
        console.log("renaming " + src + " to " + dest);
        return callback();
      }
      return callback(new Error("cannot rename " + src + " to " + dest +  ": file already exists. (Use --force to overwrite files)"));
    });
  });
}

// args: object
function main(args, callback) {
  // args._ is where yargs puts all arguments not associated with flags
  // apparently yargs also globs filenames.
  var filenames = args._;

  async.each(filenames, function (file, callback) {
    fs.stat(file, function (err, stats) {
      if (err) {
        return callback(new Error("cannot stat file: '" + file + "': No such file or directory"));
      }
      if (stats.isFile()) {
        renameEpisodeFile(file, args, callback);
      } else if (stats.isDirectory()) {
        console.log("Cannot rename " + file + ": Is a directory");
      }
    });
  }, callback);
}

if (require.main === module) {
  if (argv.version) {
    console.log(packageJson.version);
    process.exit(0);
  }
  if (argv.help) {
    yargs.showHelp();
    process.exit(0);
  }
  if (argv._.length === 0) {
    console.log(episoder + ": missing file operand.");
    console.log("Try '" + episoder + " --help' for more information.");
    process.exit(1);
  }
  main(argv, function (err) {
    if (err) {
      console.error(err.message);
    }
  });
}

// exports for tests...
exports.parseFilename = parseFilename;
exports.getReplacementFilename = getReplacementFilename;
exports.renameEpisodeFile = renameEpisodeFile;
exports.main = main;
exports.trakt = trakt;
exports.argv = argv;
<commit_msg>corrected typo<commit_after>#!/usr/bin/env node
/*jslint node:true, indent:2, regexp:true, nomen:true */
"use strict";

var yargs = require("yargs")
    .usage("Rename downloaded TV episode files.\n $0 [options] [file]")
    .example("$0 community-s01e04.avi", "rename file to Community - S01E04 - Social Psychology.avi")
    .example("$0 --offset=-1 community-s01e04.avi", "rename file to Community - S01E03 - Introduction to Film.avi")
    .example("$0 -S Community -s 1 -e 2 unknown.avi", "rename file to Community - S01E02 - Spanish 101.avi")
    .describe("v", "Display version number")
    .alias("v", "version")
    .describe("h", "Display help")
    .alias("h", "help")
    .describe("S", "Stipulate showname")
    .alias("S", "show")
    .describe("s", "Stipulate season number")
    .alias("s", "season")
    .describe("e", "Stipulate episode number")
    .alias("e", "episode")
    .describe("o", "Offset episode number")
    .alias("o", "offset")
    .describe("f", "Force overwrite of existing files.")
    .alias("f", "force")
    .describe("save_api_key", "Saves your trakt api key"),
  argv = yargs.argv,
  episoder = argv.$0,
  trakt,
  path = require("path"),
  configPath = path.join(__dirname, "..", "config", "traktApiKey.js"),
  fs = require("fs"),
  async = require("async"),
  titleCase = require("to-title-case"),
  errCouldNotRename = "unable to rename ",
  zPad = require("./helpers.js").zPad,
  sanitize = require("sanitize-filename"),
  packageJson = require("../package.json");

if (argv.save_api_key) {
  // using synchronous method, because nothing can happen until this has finished:
  try {
    /*jslint stupid: true*/
    fs.writeFileSync(configPath, "module.exports = '" + argv.save_api_key + "';\n");
    /*jslint stupid: false*/
  } catch (e) {
    if (e.code === "EACCES") {
      console.log("Permission denied. You probably need to be root to write this file.");
      process.exit(1);
    }
    console.log("There was an unexpected error writing your file:", JSON.stringify(e));
    process.exit(1);
  }
  console.log("Your api key has been written to file.");
  process.exit(0);
} else {
  trakt = require("./trakt");
}

// filename: string
// options: object
function parseFilename(filename, options) {
  var ext = path.extname(filename).toLowerCase(),
  // the following regex should match:
  //   Community S01E04.mp4
  //   Community s01e04.mp4
  //   Community 1x04.mp4
  //   Community 1-04.mp4
    re = /(.*)\D(\d{1,2})[ex\-](\d{1,2})/i,
    searchResults = filename.match(re),
    show,
    season,
    episode,
    offset,
    episodeObject = {};

  if (options === undefined) {
    options = {};
  }

  offset = options.offset || 0;
  if (searchResults === null) {
    // this regex should match:
    //   Community Season 1 Episode 4.mp4
    // (case insensitive)
    re = /(.*)Season.*?(\d{1,2}).*Episode\D*?(\d{1,2})/i;
    searchResults = filename.match(re);
  }

  if (searchResults === null) {
    // this regex should match:
    //   Community 104.mp4
    re = /(.*)\D(\d)(\d\d)\D/;
    searchResults = filename.match(re);
  }

  if (searchResults === null && options.season) {
    // this regex should match:
    //   Community 04.mp4
    // but only if we've specified a season with season flag
    re = /(.*)\D(\d+)\D/;
    searchResults = filename.match(re);
  }

  if (searchResults === null && options.season && options.show) {
    // this regex should match:
    //   04.mp4
    // but only if we've specified a season and show with flags
    re = /(\d+)\D/;
    searchResults = filename.match(re);
  }

  try {
    show = options.show || searchResults[1];
  } catch (e) {
    return null;
  }
  show = titleCase(show
      // remove hanging characters
      .replace(/^[\-.\s]+|[\-.\s]+$/g, "")
      .trim());

  if (options.episode) {
    episode = options.episode + offset;
    if (searchResults !== null) {
      searchResults.pop();
    }
  } else {
    try {
      episode = Number(searchResults.pop()) + offset;
    } catch (e) {
      return null;
    }
  }

  season = options.season || Number(searchResults.pop());

  episodeObject = {
    originalFilename: filename,
    show: show,
    season: season,
    episode: episode,
    extension: ext
  };
  return episodeObject;
}

// filename: string
// options: object
function getReplacementFilename(filename, options, callback) {
  var episodeObject = parseFilename(filename, options);

  if (episodeObject === null) {
    return callback(errCouldNotRename + filename);
  }

  trakt.getTitle(episodeObject, function (err, episodeObject) {
    if (err) {
      callback(err);
      return;
    }
    var show = episodeObject.show,
      season = zPad(episodeObject.season),
      episode = "E" + zPad(episodeObject.episode),
      title = episodeObject.title,
      ext = episodeObject.extension,
      replacementFilename;

    if (isNaN(Number(season))) {
      season = titleCase(season) + " ";
    } else {
      season = "S" + season;
    }
    replacementFilename = sanitize(show + " - " + season + episode + " - " + title +  ext);

    callback(null, replacementFilename);
  });
}

// src: filepath string
// options: object
function renameEpisodeFile(src, options, callback) {
  var filename = path.basename(src),
    dir = path.dirname(src);

  getReplacementFilename(filename, options, function (err, replacementFilename) {
    if (err) {
      console.error(err);
      return;
    }

    var dest = path.join(dir, replacementFilename);

    fs.stat(dest, function (err) {
      if ((err && err.code === "ENOENT") || (options && options.force)) {
        fs.rename(src, dest);
        console.log("renaming " + src + " to " + dest);
        return callback();
      }
      return callback(new Error("cannot rename " + src + " to " + dest +  ": file already exists. (Use --force to overwrite files)"));
    });
  });
}

// args: object
function main(args, callback) {
  // args._ is where yargs puts all arguments not associated with flags
  // apparently yargs also globs filenames.
  var filenames = args._;

  async.each(filenames, function (file, callback) {
    fs.stat(file, function (err, stats) {
      if (err) {
        return callback(new Error("cannot stat file: '" + file + "': No such file or directory"));
      }
      if (stats.isFile()) {
        renameEpisodeFile(file, args, callback);
      } else if (stats.isDirectory()) {
        console.log("Cannot rename " + file + ": Is a directory");
      }
    });
  }, callback);
}

if (require.main === module) {
  if (argv.version) {
    console.log(packageJson.version);
    process.exit(0);
  }
  if (argv.help) {
    yargs.showHelp();
    process.exit(0);
  }
  if (argv._.length === 0) {
    console.log(episoder + ": missing file operand.");
    console.log("Try '" + episoder + " --help' for more information.");
    process.exit(1);
  }
  main(argv, function (err) {
    if (err) {
      console.error(err.message);
    }
  });
}

// exports for tests...
exports.parseFilename = parseFilename;
exports.getReplacementFilename = getReplacementFilename;
exports.renameEpisodeFile = renameEpisodeFile;
exports.main = main;
exports.trakt = trakt;
exports.argv = argv;
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var Graph = require('../');

function getSimpleGraph() {
    var g = new Graph();

    g.addVertex('A');
    g.addVertex('B');
    g.addVertex('C');
    g.addVertex('D');

    g.addEdge('A', 'B');
    g.addEdge('A', 'C');
    g.addEdge('C', 'D');

    return g;
}

function getComplexGraph() {
    var g = new Graph();

    g.addVertex('A');
    g.addVertex('B');
    g.addVertex('C');
    g.addVertex('D');
    g.addVertex('E');
    g.addVertex('F');
    g.addVertex('G');
    g.addVertex('H');

    g.addEdge('A', 'B');
    g.addEdge('A', 'E');
    g.addEdge('B', 'C');
    g.addEdge('B', 'F');
    g.addEdge('C', 'D');
    g.addEdge('C', 'H');
    g.addEdge('D', 'H');
    g.addEdge('E', 'F');
    g.addEdge('F', 'G');

    return g;
}

describe('Graph data structure', function () {
    it('instantiates a graph instance with no parameters', function () {
        var graph = new Graph();
        expect(graph).to.exist;
    });

    it('instantiates a graph instance provided an arrary of vertices', function () {
        var graph = new Graph(['A', 'B', 'C', 'D']);
        expect(graph.vertices).to.be.Array;
        expect(graph.vertices).to.have.length(4);
    });

    it('when instantiated creates a vertices Array', function () {
        var graph = new Graph();
        expect(graph.vertices).to.exist;
        expect(graph.vertices).to.be.Array;
    });

    it('when instantiated creates an adjacency list', function () {
        var graph = new Graph();
        expect(graph.adjacentList).to.exist;
    });

    it('adds a vertex to the graph', function () {
        var graph = getSimpleGraph();
        expect(graph.vertices).to.have.length(4);
    });

    it('adds an edge between vertices', function () {
        var graph = getSimpleGraph();

        expect(graph.adjacentList.get('A')).to.be.an.Array;
        expect(graph.adjacentList.get('A')[0]).to.include({label: 'B', weight: 1});
        expect(graph.adjacentList.get('A')[1]).to.include({label: 'C', weight: 1});

        expect(graph.adjacentList.get('B')).to.be.an.Array;
        expect(graph.adjacentList.get('B')[0]).to.include({label: 'A', weight: 1});

        expect(graph.adjacentList.get('D')).to.be.an.Array;
        expect(graph.adjacentList.get('D')[0]).to.include({label: 'C', weight: 1});
    });

    it('does a breadth first search', function () {
        var graph = getComplexGraph();
        expect(graph.BFS('A')).to.be.eql(['A', 'B', 'E', 'C', 'F', 'D', 'H', 'G']);
    });

    it('does a depth first search', function() {
        var graph = getComplexGraph();
        expect(graph.DFS('A')).to.be.eql(['A', 'E', 'F', 'G', 'B', 'C', 'H', 'D']);
    });

});
<commit_msg>Add tests to complete test coverage<commit_after>var expect = require('chai').expect;
var Graph = require('../');

function getSimpleGraph() {
    var g = new Graph();

    g.addVertex('A');
    g.addVertex('B');
    g.addVertex('C');
    g.addVertex('D');

    g.addEdge('A', 'B');
    g.addEdge('A', 'C');
    g.addEdge('C', 'D');

    return g;
}

function getComplexGraph() {
    var g = new Graph();

    g.addVertex('A');
    g.addVertex('B');
    g.addVertex('C');
    g.addVertex('D');
    g.addVertex('E');
    g.addVertex('F');
    g.addVertex('G');
    g.addVertex('H');

    g.addEdge('A', 'B');
    g.addEdge('A', 'E');
    g.addEdge('B', 'C');
    g.addEdge('B', 'F');
    g.addEdge('C', 'D');
    g.addEdge('C', 'H');
    g.addEdge('D', 'H');
    g.addEdge('E', 'F');
    g.addEdge('F', 'G');

    return g;
}

describe('Graph data structure', function () {
    it('instantiates a graph instance with no parameters', function () {
        var graph = new Graph();
        expect(graph).to.exist;
    });

    it('instantiates a graph instance provided an arrary of vertices', function () {
        var graph = new Graph(['A', 'B', 'C', 'D']);
        expect(graph.vertices).to.be.Array;
        expect(graph.vertices).to.have.length(4);
    });

    it('when instantiated creates a vertices Array', function () {
        var graph = new Graph();
        expect(graph.vertices).to.exist;
        expect(graph.vertices).to.be.Array;
    });

    it('when instantiated creates an adjacency list', function () {
        var graph = new Graph();
        expect(graph.adjacentList).to.exist;
    });

    it('has toString method to provide a string representation of the graph', function () {
        var graph = getSimpleGraph();
        expect(graph.toString()).to.be.a('string');
    });

    it('adds a vertex to the graph', function () {
        var graph = getSimpleGraph();
        expect(graph.vertices).to.have.length(4);
    });

    it('adds an edge between vertices', function () {
        var graph = getSimpleGraph();

        expect(graph.adjacentList.get('A')).to.be.an.Array;
        expect(graph.adjacentList.get('A')[0]).to.include({label: 'B', weight: 1});
        expect(graph.adjacentList.get('A')[1]).to.include({label: 'C', weight: 1});

        expect(graph.adjacentList.get('B')).to.be.an.Array;
        expect(graph.adjacentList.get('B')[0]).to.include({label: 'A', weight: 1});

        expect(graph.adjacentList.get('D')).to.be.an.Array;
        expect(graph.adjacentList.get('D')[0]).to.include({label: 'C', weight: 1});
    });

    it('throws an error if adding edge to a vertex that does not exist', function () {
        var graph = getSimpleGraph();
        expect(function () {
            graph.addEdge('Z', 'A');
        }).to.throw('Vertex does not exist: Cannot add edge.');
    });

    it('does a breadth first search', function () {
        var graph = getComplexGraph();
        expect(graph.BFS('A')).to.be.eql(['A', 'B', 'E', 'C', 'F', 'D', 'H', 'G']);
    });

    it('does a depth first search', function() {
        var graph = getComplexGraph();
        expect(graph.DFS('A')).to.be.eql(['A', 'E', 'F', 'G', 'B', 'C', 'H', 'D']);
    });

});
<|endoftext|>


<commit_before>import Effect from '../Effect.js';
Effect.disable = true;
import Game from '../Game.js';
import aiSearch from './search.js';

onmessage = function (e) {
	const {
		id,
		data: { data, moves },
	} = e.data;
	const game = new Game(data);
	for (const move of moves) {
		game.next(move);
	}
	postMessage({ id, cmd: aiSearch(game) });
};
<commit_msg>Greatly increase AI speed by having worker cache move list to avoid replaying all moves each move<commit_after>import Effect from '../Effect.js';
Effect.disable = true;
import Game from '../Game.js';
import aiSearch from './search.js';

let game = null,
	premoves = null;
onmessage = function (e) {
	const {
		id,
		data: { data, moves },
	} = e.data;
	if (
		game === null ||
		premoves.length > moves.length ||
		JSON.stringify(data) !== JSON.stringify(game.data) ||
		JSON.stringify(premoves) !== JSON.stringify(moves.slice(0, premoves.length))
	) {
		game = new Game(data);
		premoves = null;
	}
	for (let i = premoves !== null ? premoves.length : 0; i < moves.length; i++) {
		game.next(moves[i]);
	}
	premoves = moves;
	postMessage({ id, cmd: aiSearch(game) });
};
<|endoftext|>


<commit_before>'use strict';

var crypto = require('crypto');
var utils = require('../utils');
var InstanceResource = require('./InstanceResource');

function ApiKey() {
  ApiKey.super_.apply(this, arguments);
}
utils.inherits(ApiKey, InstanceResource);

ApiKey.prototype.getDecryptedSecret = function(callback) {
  var self = this;
  var salt = self.apiKeyMetaData.encryptionKeySalt;
  var iterations = self.apiKeyMetaData.encryptionKeyIterations;
  var keyLengthBits = self.apiKeyMetaData.encryptionKeySize;
  var password = new Buffer(self.dataStore.requestExecutor.options.apiKey.secret);
  var encryptedSecret = new Buffer(self.secret,'base64');
  var iv = encryptedSecret.slice(0,16);
  var rawEncryptedValue = encryptedSecret.slice(16);
  crypto.pbkdf2(password,new Buffer(salt,'base64'),iterations,(keyLengthBits/8),function(err,key){
    if(err){
      return callback(err);
    }
    var decrypted;
    try{
      var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
      decrypted = decipher.update(rawEncryptedValue,'hex','utf8');
      decrypted += decipher.final('utf8');
    }catch(e){
      return callback(e);
    }
    callback(null,decrypted);
  });

};

ApiKey.prototype.decrypt = function(callback) {
  var self = this;
  if(self.apiKeyMetaData){
    self.getDecryptedSecret(function(err,secret){
      if(err){
        return callback(err);
      }
      self.secret = secret;
      delete self.apiKeyMetaData;
      callback(null,self);
    });
  }else{
    process.nextTick(callback.bind(null,null,self.secret));
  }
};

module.exports = ApiKey;<commit_msg>fix decryption strategy for node v0.8.0<commit_after>'use strict';

var crypto = require('crypto');
var utils = require('../utils');
var InstanceResource = require('./InstanceResource');

function ApiKey() {
  ApiKey.super_.apply(this, arguments);
}
utils.inherits(ApiKey, InstanceResource);

ApiKey.prototype.getDecryptedSecret = function(callback) {
  var self = this;
  var salt = self.apiKeyMetaData.encryptionKeySalt;
  var iterations = self.apiKeyMetaData.encryptionKeyIterations;
  var keyLengthBits = self.apiKeyMetaData.encryptionKeySize;
  var password = new Buffer(self.dataStore.requestExecutor.options.apiKey.secret);
  var encryptedSecret = new Buffer(self.secret,'base64');
  var iv = encryptedSecret.slice(0,16);
  var rawEncryptedValue = encryptedSecret.slice(16);
  crypto.pbkdf2(password,new Buffer(salt,'base64'),iterations,(keyLengthBits/8),function(err,key){
    if(err){
      return callback(err);
    }
    var decrypted;
    try{
      var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
      decrypted = decipher.update(rawEncryptedValue,'binary','utf8');
      decrypted += decipher.final('utf8');
    }catch(e){
      return callback(e);
    }
    callback(null,decrypted);
  });

};

ApiKey.prototype.decrypt = function(callback) {
  var self = this;
  if(self.apiKeyMetaData){
    self.getDecryptedSecret(function(err,secret){
      if(err){
        return callback(err);
      }
      self.secret = secret;
      delete self.apiKeyMetaData;
      callback(null,self);
    });
  }else{
    process.nextTick(callback.bind(null,null,self.secret));
  }
};

module.exports = ApiKey;<|endoftext|>


<commit_before>import { xPath, xPathArray } from '../utils';

function swapAndPair(values) {
  const out = [];

  for (let i = 0; i < values.length; i += 2) {
    const lat = values[i];
    const lon = values[i + 1];
    out.push([lon, lat]);
  }
  return out;
}

function parseGeometryValues(value) {
  const values = value.trim().split(/\s+/).map(parseFloat);
  return swapAndPair(values);
}

function parseGmlLine(node, resolver) {
  return swapAndPair(xPath(node, 'gml:posList/text()', resolver)
    .trim()
    .split(/\s+/)
    .map(parseFloat));
}

function parseGmlPolygon(node, resolver) {
  const exterior = parseGmlLine(
    xPath(node, 'gml:exterior/gml:LinearRing', resolver), resolver
  );
  const interiors = xPathArray(node, 'gml:interior/gml:LinearRing', resolver)
    .map(interior => parseGmlLine(interior, resolver));
  return [exterior, ...interiors];
}

function parseGml(node) {
  const resolver = (prefix) => {
    if (prefix === 'gml') {
      return node.namespaceURI;
    }
    return null;
  };

  switch (node.localName) {
    case 'Point': {
      const coordinates = xPath(node, 'gml:pos/text()', resolver)
        .trim()
        .split(/\s+/)
        .map(parseFloat);
      return {
        type: 'Point',
        coordinates: [coordinates[1], coordinates[0]],
      };
    }
    case 'LineString': {
      const coordinates = parseGmlLine(node, resolver);
      return {
        type: 'LineString',
        coordinates,
      };
    }
    case 'Polygon': {
      const coordinates = parseGmlPolygon(node, resolver);
      return {
        type: 'Polygon',
        coordinates,
      };
    }
    case 'Envelope': {
      // TODO:
      break;
    }
    case 'MultiSurface': {
      const coordinates = xPathArray(node, 'gml:surfaceMembers/gml:Polygon', resolver)
        .map(polygon => parseGmlPolygon(polygon, resolver));
      return {
        type: 'MultiPolygon',
        coordinates,
      };
    }
    default:
      break;
  }
  return null;
}

export class BaseFeedFormat {
  parseGeometry(node) {
    const where = xPath(node, 'georss:where');
    const point = xPath(node, 'georss:point/text()');
    const line = xPath(node, 'georss:line/text()');
    const polygon = xPath(node, 'georss:polygon/text()');

    if (where) {
      return parseGml(xPath(where, '*'));
    } else if (point) {
      return {
        type: 'Point',
        geometry: parseGeometryValues(point)[0],
      };
    } else if (line) {
      return {
        type: 'LineString',
        geometry: parseGeometryValues(line),
      };
    } else if (polygon) {
      return {
        type: 'Polygon',
        geometry: parseGeometryValues(line),
      };
    }
    return null;
  }

  parseBox(node) {
    const box = xPath(node, 'georss:box/text()');
    if (box) {
      const values = box.split(/\s+/).map(parseFloat);
      return [values[1], values[0], values[3], values[2]];
    }
    return null;
  }

  parseDate(node) {
    const date = xPath(node, 'dc:date/text()');
    if (date) {
      const values = date.split('/');
      if (values.length === 1) {
        return new Date(date);
      } else if (values.length >= 1) {
        return [new Date(values[0]), new Date(values[1])];
      }
    }
    return null;
  }

  parseLinks(node) {
    return xPathArray(node, 'atom:link').map(linkNode => {
      const link = {
        href: linkNode.getAttribute('href'),
      };
      const rel = linkNode.getAttribute('rel');
      const type = linkNode.getAttribute('type');
      const title = linkNode.getAttribute('title');
      if (rel) {
        link.rel = rel;
      }
      if (type) {
        link.type = type;
      }
      if (title) {
        link.title = title;
      }

      return link;
    });
  }

  parseMedia(node) {
    return xPathArray(node, 'media:content').map(mediaNode => ({
      url: mediaNode.getAttribute('url'),
      category: xPath(mediaNode, 'media:category/text()'),
      scheme: xPath(mediaNode, 'media:category/@scheme'),
    }));
  }
}
<commit_msg>Fixing xPath to get polygons from multiSurfaces.<commit_after>import { xPath, xPathArray } from '../utils';

function swapAndPair(values) {
  const out = [];

  for (let i = 0; i < values.length; i += 2) {
    const lat = values[i];
    const lon = values[i + 1];
    out.push([lon, lat]);
  }
  return out;
}

function parseGeometryValues(value) {
  const values = value.trim().split(/\s+/).map(parseFloat);
  return swapAndPair(values);
}

function parseGmlLine(node, resolver) {
  return swapAndPair(xPath(node, 'gml:posList/text()', resolver)
    .trim()
    .split(/\s+/)
    .map(parseFloat));
}

function parseGmlPolygon(node, resolver) {
  const exterior = parseGmlLine(
    xPath(node, 'gml:exterior/gml:LinearRing', resolver), resolver
  );
  const interiors = xPathArray(node, 'gml:interior/gml:LinearRing', resolver)
    .map(interior => parseGmlLine(interior, resolver));
  return [exterior, ...interiors];
}

function parseGml(node) {
  const resolver = (prefix) => {
    if (prefix === 'gml') {
      return node.namespaceURI;
    }
    return null;
  };

  switch (node.localName) {
    case 'Point': {
      const coordinates = xPath(node, 'gml:pos/text()', resolver)
        .trim()
        .split(/\s+/)
        .map(parseFloat);
      return {
        type: 'Point',
        coordinates: [coordinates[1], coordinates[0]],
      };
    }
    case 'LineString': {
      const coordinates = parseGmlLine(node, resolver);
      return {
        type: 'LineString',
        coordinates,
      };
    }
    case 'Polygon': {
      const coordinates = parseGmlPolygon(node, resolver);
      return {
        type: 'Polygon',
        coordinates,
      };
    }
    case 'Envelope': {
      // TODO:
      break;
    }
    case 'MultiSurface': {
      const coordinates = xPathArray(node, 'gml:surfaceMembers/gml:Polygon|gml:surfaceMember/gml:Polygon', resolver)
        .map(polygon => parseGmlPolygon(polygon, resolver));
      return {
        type: 'MultiPolygon',
        coordinates,
      };
    }
    default:
      break;
  }
  return null;
}

export class BaseFeedFormat {
  parseGeometry(node) {
    const where = xPath(node, 'georss:where');
    const point = xPath(node, 'georss:point/text()');
    const line = xPath(node, 'georss:line/text()');
    const polygon = xPath(node, 'georss:polygon/text()');

    if (where) {
      return parseGml(xPath(where, '*'));
    } else if (point) {
      return {
        type: 'Point',
        geometry: parseGeometryValues(point)[0],
      };
    } else if (line) {
      return {
        type: 'LineString',
        geometry: parseGeometryValues(line),
      };
    } else if (polygon) {
      return {
        type: 'Polygon',
        geometry: parseGeometryValues(line),
      };
    }
    return null;
  }

  parseBox(node) {
    const box = xPath(node, 'georss:box/text()');
    if (box) {
      const values = box.split(/\s+/).map(parseFloat);
      return [values[1], values[0], values[3], values[2]];
    }
    return null;
  }

  parseDate(node) {
    const date = xPath(node, 'dc:date/text()');
    if (date) {
      const values = date.split('/');
      if (values.length === 1) {
        return new Date(date);
      } else if (values.length >= 1) {
        return [new Date(values[0]), new Date(values[1])];
      }
    }
    return null;
  }

  parseLinks(node) {
    return xPathArray(node, 'atom:link').map(linkNode => {
      const link = {
        href: linkNode.getAttribute('href'),
      };
      const rel = linkNode.getAttribute('rel');
      const type = linkNode.getAttribute('type');
      const title = linkNode.getAttribute('title');
      if (rel) {
        link.rel = rel;
      }
      if (type) {
        link.type = type;
      }
      if (title) {
        link.title = title;
      }

      return link;
    });
  }

  parseMedia(node) {
    return xPathArray(node, 'media:content').map(mediaNode => ({
      url: mediaNode.getAttribute('url'),
      category: xPath(mediaNode, 'media:category/text()'),
      scheme: xPath(mediaNode, 'media:category/@scheme'),
    }));
  }
}
<|endoftext|>


<commit_before>define({
    "postbox-text": "Escriba su comentario aquí (al menos 3 caracteres)",
    "postbox-author": "Nombre (opcional)",
    "postbox-email": "E-mail (opcional)",
    "postbox-website": "Sitio web (opcional)",
    "postbox-preview": "Avance",
    "postbox-edit": "Editar",
    "postbox-submit": "Enviar",
    "num-comments": "Un Comentario\n{{ n }} Comentarios",
    "no-comments": "Sin Comentarios Todavía",
    "comment-reply": "Responder",
    "comment-edit": "Editar",
    "comment-save": "Guardar",
    "comment-delete": "Eliminar",
    "comment-confirm": "Confirmar",
    "comment-close": "Cerrar",
    "comment-cancel": "Cancelar",
    "comment-deleted": "Comentario eliminado.",
    "comment-queued": "Comentario en espera para moderación.",
    "comment-anonymous": "Anónimo",
    "comment-hidden": "{{ n }} Oculto(s)",
    "date-now": "ahora",
    "date-minute": "hace un minuto\nhace {{ n }} minutos",
    "date-hour": "hace una hora\nhace {{ n }} horas",
    "date-day": "ayer\nHace {{ n }} días",
    "date-week": "la semana pasada\nhace {{ n }} semanas",
    "date-month": "el mes pasado\nhace {{ n }} meses",
    "date-year": "el año pasado\nhace {{ n }} años"
});
<commit_msg>update es.js<commit_after>define({
    "postbox-text": "Escriba su comentario aquí (al menos 3 caracteres)",
    "postbox-author": "Nombre (opcional)",
    "postbox-email": "E-mail (opcional)",
    "postbox-website": "Sitio web (opcional)",
    "postbox-preview": "Vista preliminar",
    "postbox-edit": "Editar",
    "postbox-submit": "Enviar",
    "num-comments": "Un Comentario\n{{ n }} Comentarios",
    "no-comments": "Sin Comentarios Todavía",
    "comment-reply": "Responder",
    "comment-edit": "Editar",
    "comment-save": "Guardar",
    "comment-delete": "Eliminar",
    "comment-confirm": "Confirmar",
    "comment-close": "Cerrar",
    "comment-cancel": "Cancelar",
    "comment-deleted": "Comentario eliminado.",
    "comment-queued": "Comentario en espera para moderación.",
    "comment-anonymous": "Anónimo",
    "comment-hidden": "{{ n }} Oculto(s)",
    "date-now": "ahora",
    "date-minute": "hace un minuto\nhace {{ n }} minutos",
    "date-hour": "hace una hora\nhace {{ n }} horas",
    "date-day": "ayer\nHace {{ n }} días",
    "date-week": "la semana pasada\nhace {{ n }} semanas",
    "date-month": "el mes pasado\nhace {{ n }} meses",
    "date-year": "el año pasado\nhace {{ n }} años"
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */
'use strict';

const getPolyfills = require('./rn-get-polyfills');

/**
 * This cli config is needed for development purposes, e.g. for running
 * integration tests during local development or on CI services.
 */
module.exports = {
  extraNodeModules: {
    'react-native': __dirname,
  },
  serializer: {
    getModulesRunBeforeMainModule: () => [
      require.resolve('./Libraries/Core/InitializeCore'),
    ],
    getPolyfills,
  },
  resolver: {
    hasteImplModulePath: require.resolve('./jest/hasteImpl'),
  },
  transformer: {
    assetRegistryPath: require.resolve('./Libraries/Image/AssetRegistry'),
  },
};
<commit_msg>Enable inline requires for RNTester (#23881)<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */
'use strict';

const getPolyfills = require('./rn-get-polyfills');

/**
 * This cli config is needed for development purposes, e.g. for running
 * integration tests during local development or on CI services.
 */
module.exports = {
  extraNodeModules: {
    'react-native': __dirname,
  },
  serializer: {
    getModulesRunBeforeMainModule: () => [
      require.resolve('./Libraries/Core/InitializeCore'),
    ],
    getPolyfills,
  },
  resolver: {
    hasteImplModulePath: require.resolve('./jest/hasteImpl'),
  },
  transformer: {
    getTransformOptions: () => ({
      transform: {
        experimentalImportSupport: true,
        inlineRequires: true,
      },
    }),
    assetRegistryPath: require.resolve('./Libraries/Image/AssetRegistry'),
  },
};
<|endoftext|>


<commit_before>// Firefox Add-on Requires
var self = require('sdk/self'),
    buttons = require('sdk/ui/button/action'),
    panels = require('sdk/panel'),
    Request = require('sdk/request').Request,
    tmr = require('sdk/timers'),
    notifications = require('sdk/notifications'),
    tabs = require('sdk/tabs'),
    _ = require('sdk/l10n').get,
    platform = require("sdk/system").platform,
    CLIENT_ID = require('sdk/simple-prefs').prefs['clientID']
    settings = require('./settings.js');

// API Urls
var streamAPI = 'https://api.twitch.tv/kraken/streams/',
    gameAPI = 'https://api.twitch.tv/kraken/games/top',
    followingAPI = 'https://api.twitch.tv/kraken/users/{0}/follows/channels',
    featuredAPI = 'https://api.twitch.tv/kraken/streams/featured',
    searchAPI = 'https://api.twitch.tv/kraken/search/streams';

var isFirst = true,
    followingNames = [],
    liveFollowings = [],
    newLiveFollowings = [],
    intervalID = null;

// Executes after extension loads.
exports.main = function(options, callbacks) {
    // Preparing simple storage settings if not exist
    settings.prepare(options.loadReason);

    // Localization variables
    var viewers = ' ' + _('viewers'),
        channels = ' ' + _('channels'),
        playing = _('playing'),
        signIn = _('sign_in'),
        signOut = _('sign_out'),
        noStreams = _('no_streams'),
        searchMessage = _('search_message');

    // Action Button Initialize
    var button = buttons.ActionButton({
        id: 'twitch-live',
        label: 'Twitch Live',
        icon: {
            '16': './icons/twitch-icon-32.png',
            '32': './icons/twitch-icon-32.png',
            '64': './icons/twitch-icon-64.png',
            '128': './icons/twitch-icon-128.png'
        },
        onClick: handleClick
    });

    // Panel Initialize
    var panel = panels.Panel({
        contentURL: self.data.url('panel.html'),
        width: 400,
        height: 555
    });

    // Action Button Click Handler
    function handleClick(state) {
        panel.show({
            position: button
        });
    }

    function include(arr, obj) {
        return (arr.indexOf(obj) != -1);
    }

    /* String format function */
    function strFormat() {
        var s = arguments[0];
      for (var i = 0; i < arguments.length - 1; i++) {
          var reg = new RegExp("\\{" + i + "\\}", "gm");
          s = s.replace(reg, arguments[i + 1]);
      }
      return s;
    }

    /**
     * Port to get online following channels.
     * Send all followed channels to printFollowing() function for API request.
     */
    panel.port.on('getFollowings', function() {
        isFirst = true;

        if (followingNames.length > 0) {
            for (var i = 0; i < followingNames.length; i++) {
                if (i == followingNames.length - 1) {
                    printFollowing(followingNames[i], true);
                } else {
                    printFollowing(followingNames[i]);
                }
            }
        } else {
            panel.port.emit('noFollowingStream', noStreams);
        }
    });

    /**
     * Get followed channels one by one and send a API request.
     * If channels if online return port for channel information.
     */
    function printFollowing(name, isLast) {
        Request ({
            url: streamAPI + encodeURIComponent(name),
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                if (response.json.stream != null) {
                    if (isFirst === true) {
                        isFirst = false;
                    }
                    panel.port.emit('followResponse', response.json, viewers, playing);
                }

                // If there is no online channel, then it give a warning
                if (isFirst == true && isLast == true) {
                    panel.port.emit('noFollowingStream', noStreams);
                }
            }
        }).get();
    }

    /**
     * Port to get top 50 games.
     * Return a port for game informations.
     */
    panel.port.on('printGames', function() {
        Request ({
            url: gameAPI,
            content: {
                limit: 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('gameResponse', response.json, viewers, channels);
            }
        }).get();
    });

    /**
     * Port to get top 50 streams by popularity.
     * Return a port for stream informations.
     */
    panel.port.on('printStreams', function() {
        Request ({
            url: streamAPI,
            content: {
                limit: 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('streamResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Port to get top 50 streams by game. This port is used for second page of the game tab.
     * Return a port for stream informations.
     */
    panel.port.on('getStreamsByGame', function(gameName) {
        Request ({
            url: streamAPI,
            content: {
                game : gameName,
                limit : 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('streamByGameResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Port to get top 50 featured streams.
     * Return a port for stream informations.
     */
    panel.port.on('getFeatured', function() {
        Request ({
            url: featuredAPI,
            content: {
                limit: 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('featuredResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Port to get search result by query.
     * Return a port for search result
     */
    panel.port.on('search', function(query) {
        Request ({
            url: searchAPI,
            content: {
                q: encodeURIComponent(query)
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('searchResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Call initializeNotify() for initialize notifications.
     */
    panel.port.on('initialize', function() {
        initializeNotify();
    });

    /**
     * Get followers and initialize notifications.
     * Notifications send in every 5 minutes.
     */
    function initializeNotify() {
        var username = settings.getStorageItem('twitchName');

        if (username != undefined && username != '') {
            followingCheck(username, function() {
                // Firstly we are initializing a postnotify and then
                // setting it to do that for 5mins(300000 ms) repeatedly.
                if(settings.getStorageItem('notification') == true) {
                    postNotify();
                    intervalID = tmr.setInterval(postNotify, 300000); // 5 * 60 * 100
                }
            });
        }
    }

    /**
     * Send API request to get all followers.
     * Used in initialize port and for refresh the followers.
     */
    function followingCheck(username, callback) {
        if (username != undefined) {
            var followUrl = strFormat(followingAPI, encodeURIComponent(username));

            Request ({
                url: followUrl,
                content: {
                    limit: 500
                },
                headers: {
                    'Client-ID': CLIENT_ID,
                    'Cache-control': 'no-cache'
                },
                onComplete: function(response) {
                    if (response.json.follows != undefined) {
                        for (var i = 0; i < response.json.follows.length; i++) {
                            followingNames[i] = response.json.follows[i].channel.name;
                        }

                        // If callback parameter is filled, execute it.
                        if (callback && typeof(callback) === 'function') {
                            callback();
                        }
                    }
                }
            }).get();
        };
    }

    /**
     * Execute in every 5 minutes and check for the new online streamers.
     * If there is online streamer then send it to notify function to show notify.
     */
    function postNotify() {
        var a = 0;
        var index = 0;
        var streamersText = '';
        var isLiveEmpty = liveFollowings.length == 0;
        var lastUrl = '';

        if (followingNames.length > 0) {
            for (var i = 0; i < followingNames.length; i++) {
                Request ({
                    url: streamAPI + encodeURIComponent(followingNames[i]),
                    headers: {
                        'Client-ID': CLIENT_ID,
                        'Cache-control': 'no-cache'
                    },
                    onComplete: function(response) {

                        // If live followigs empty, then here executes, otherwise goes to else for comparison
                        if (isLiveEmpty) {
                            if (response.json.stream != undefined) {
                                if (response.json.stream.channel.display_name !== undefined) {
                                    liveFollowings[a++] = response.json.stream.channel.display_name;
                                    lastUrl = response.json.stream.channel.url;
                                }
                            }

                            index++;
                            if (index == followingNames.length) {
                                streamersText = liveFollowings.join(', ');
                                streamersText += ' ' + _('now_online');

                                notify(streamersText, a, false, lastUrl);
                            }

                        } else {
                            if (response.json.stream != undefined) {
                                if (response.json.stream.channel.display_name !== undefined) {
                                    newLiveFollowings[a++] = response.json.stream.channel.display_name;
                                    if (!include(liveFollowings, response.json.stream.channel.display_name))
                                        lastUrl = response.json.stream.channel.url;
                                }
                            }

                            index++;
                            if (index == followingNames.length) {
                                var nowOnlineCount = 0;

                                for (var j = 0; j < newLiveFollowings.length; j++) {
                                    if (!include(liveFollowings, newLiveFollowings[j])) {
                                        streamersText += newLiveFollowings[j] + ', ';
                                        nowOnlineCount++;
                                    }
                                }

                                if (nowOnlineCount != 0) {
                                    streamersText = streamersText.slice(0, -2); // to delete the last ', ' characters.
                                    streamersText += ' ' + _('now_online');
                                    notify(streamersText, nowOnlineCount, true, lastUrl);
                                }
                            }
                        }

                    }
                }).get();
            }
        }

    }

    /**
     * Get notify text count and show notification with these.
     * If there is one online streamer then show different
     */
    function notify(ntext, count, newLive, lastUrl) {
        if (count == 1) {
            notifications.notify({
                title: ntext,
                text: _('click_to_go'),
                iconURL: './icons/twitch-icon-128.png',
                onClick: function() {
                    openLink(lastUrl, true);
                }
            });
        } else if (count > 1) {
            notifications.notify({
                title: count + ' ' + _('streamers_online'),
                text: ntext,
                iconURL: './icons/twitch-icon-128.png',
                onClick: function() {
                    openLink('http://www.twitch.tv/directory/following', false);
                }
            });
        }

        // Play audio if OS is Windows and we have actual channels gone live, if count < 1 this method causes no notification yet sound would still play
        if (platform == 'winnt' && settings.getStorageItem('notifySound') == true && count >= 1) {
		panel.port.emit('playSound', settings.getStorageItem('notifySoundRing'));
        }

        // If newlivefollowings is not empty, pass it's content to main array and empty it.
        if (newLive) {
            liveFollowings = [];
            liveFollowings = newLiveFollowings;
            newLiveFollowings = [];
        };
    }

    /**
     * Port to call clearTimer function for clear timer.
     */
    panel.port.on('clearTimer', function() {
        clearTimer();
    });

    /**
     * Clear timer and empty the follower lists.
     */
    function clearTimer() {
        tmr.clearInterval(intervalID);
        liveFollowings = [];
        newLiveFollowings = [];
    }

    /**
     * Port for login page localization.
     */
    panel.port.on('loginPage', function() {
        var locale = {
            signIn: signIn,
            passSign: _('pass_sign'),
            welcomeMsg: _('welcome_msg'),
            username: _('username')
        };

        panel.port.emit('loginPageResponse', locale);
    });

    /**
     * Port for sign in/out localization.
     */
    panel.port.on('getSignInOut', function() {

        if (settings.getStorageItem('twitchName') == undefined) {
            panel.port.emit('getSignInOutResponse', signIn);
        } else {
            panel.port.emit('getSignInOutResponse', signOut);
        }

    });

    /**
     * Port for sign out localization.
     */
    panel.port.on('getLoadCheck', function() {
        panel.port.emit('getLoadCheckResponse', signOut);
    });

    /**
     * Port for search page beginning message localization.
     */
    panel.port.on('searchMessage', function() {
        panel.port.emit('searchMessageResponse', searchMessage);
    });

    /**
     * Port for setting localizations and values.
     */
    panel.port.on('getSettings', function(isLogged) {
        var settingsLocale = {
            refreshBtn: _('refresh_fl_btn'),
            refreshMsg: _('refresh_followed'),
            help: _('donate'),
            createdBy: _('created_by'),
            notification: settings.getStorageItem('notification'),
            notifySound: settings.getStorageItem('notifySound'),
            open: settings.getStorageItem('open'),
            notifySoundRing: settings.getStorageItem('notifySoundRing'),
            showNotifications: _('show_notifications'),
            playSound: _('play_sound'),
            openStreams: _('open_streams'),
            newTab: _('new_tab'),
            newWindow: _('new_window'),
            popout: _('popout'),
            notificationSound: _('new_tab'),
            newTab: _('new_tab'),
            notificationSound: _('notification_sound')
        };

        if (isLogged) {
            settingsLocale.logMsg = _('logged_as');
            settingsLocale.logInOut = signOut;
            panel.port.emit('settingsResponse', settingsLocale);
        } else {
            settingsLocale.logMsg = _('not_logged_yet');
            settingsLocale.logInOut = signIn;
            panel.port.emit('settingsResponse', settingsLocale);
        }
    });

    panel.port.on('changeSetting', function (setting, value) {
        settings.setStorageItem(setting, value);

        if (setting == 'notification') {
            if (value == true) {
                initializeNotify();
            } else {
                clearTimer();
            }
        }
    });

    /**
     * Port for refresh followings.
     */
    panel.port.on('refreshFollowings', function() {
        followingCheck(settings.getStorageItem('twitchName'));
    });

    /**
     * Port for Open Url and hide the panel.
     */
    panel.port.on('openPage', function(url) {
        openLink(url, true)
    });

    /**
     * Open Link depend on which open type selected in settings.
     */
    function openLink(url, isStream) {
        var openType = settings.getStorageItem('open');

        if (openType == 2) {
            tabs.open({
              url: url,
              inNewWindow: true
            });
        } else if (openType == 3 && isStream) {
            url = url + '/popout';
            tabs.open(url);
        } else {
            tabs.open(url);
        }

        panel.hide();
    }

    // Set Simple Storage Item
    panel.port.on('setStorage', function(key, value) {
        settings.setStorageItem(key, value);
    });

    // Get Simple Storage Item
    panel.port.on('getStorage', function(key) {
        panel.port.emit('getStorageResponse', settings.getStorageItem(key));
    });

    // Delete Simple Storage Item
    panel.port.on('deleteStorage', function(key) {
        settings.deleteStorageItem(key)
    });
};
<commit_msg>Refactored Notification algorithm.<commit_after>// Firefox Add-on Requires
var self = require('sdk/self'),
    buttons = require('sdk/ui/button/action'),
    panels = require('sdk/panel'),
    Request = require('sdk/request').Request,
    tmr = require('sdk/timers'),
    notifications = require('sdk/notifications'),
    tabs = require('sdk/tabs'),
    _ = require('sdk/l10n').get,
    platform = require("sdk/system").platform,
    CLIENT_ID = require('sdk/simple-prefs').prefs['clientID']
    settings = require('./settings.js');

// API Urls
var streamAPI = 'https://api.twitch.tv/kraken/streams/',
    gameAPI = 'https://api.twitch.tv/kraken/games/top',
    followingAPI = 'https://api.twitch.tv/kraken/users/{0}/follows/channels',
    featuredAPI = 'https://api.twitch.tv/kraken/streams/featured',
    searchAPI = 'https://api.twitch.tv/kraken/search/streams';

var isFirst = true,
    followingNames = [],
    liveFollowings = [],
    newLiveFollowings = [],
    intervalID = null;

// Executes after extension loads.
exports.main = function(options, callbacks) {
    // Preparing simple storage settings if not exist
    settings.prepare(options.loadReason);

    // Localization variables
    var viewers = ' ' + _('viewers'),
        channels = ' ' + _('channels'),
        playing = _('playing'),
        signIn = _('sign_in'),
        signOut = _('sign_out'),
        noStreams = _('no_streams'),
        searchMessage = _('search_message');

    // Action Button Initialize
    var button = buttons.ActionButton({
        id: 'twitch-live',
        label: 'Twitch Live',
        icon: {
            '16': './icons/twitch-icon-32.png',
            '32': './icons/twitch-icon-32.png',
            '64': './icons/twitch-icon-64.png',
            '128': './icons/twitch-icon-128.png'
        },
        onClick: handleClick
    });

    // Panel Initialize
    var panel = panels.Panel({
        contentURL: self.data.url('panel.html'),
        width: 400,
        height: 555
    });

    // Action Button Click Handler
    function handleClick(state) {
        panel.show({
            position: button
        });
    }

    function include(arr, obj) {
        return (arr.indexOf(obj) != -1);
    }

    /* String format function */
    function strFormat() {
        var s = arguments[0];
      for (var i = 0; i < arguments.length - 1; i++) {
          var reg = new RegExp("\\{" + i + "\\}", "gm");
          s = s.replace(reg, arguments[i + 1]);
      }
      return s;
    }

    /**
     * Port to get online following channels.
     * Send all followed channels to printFollowing() function for API request.
     */
    panel.port.on('getFollowings', function() {
        isFirst = true;

        if (followingNames.length > 0) {
            for (var i = 0; i < followingNames.length; i++) {
                if (i == followingNames.length - 1) {
                    printFollowing(followingNames[i], true);
                } else {
                    printFollowing(followingNames[i]);
                }
            }
        } else {
            panel.port.emit('noFollowingStream', noStreams);
        }
    });

    /**
     * Get followed channels one by one and send a API request.
     * If channels if online return port for channel information.
     */
    function printFollowing(name, isLast) {
        Request ({
            url: streamAPI + encodeURIComponent(name),
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                if (response.json.stream != null) {
                    if (isFirst === true) {
                        isFirst = false;
                    }
                    panel.port.emit('followResponse', response.json, viewers, playing);
                }

                // If there is no online channel, then it give a warning
                if (isFirst == true && isLast == true) {
                    panel.port.emit('noFollowingStream', noStreams);
                }
            }
        }).get();
    }

    /**
     * Port to get top 50 games.
     * Return a port for game informations.
     */
    panel.port.on('printGames', function() {
        Request ({
            url: gameAPI,
            content: {
                limit: 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('gameResponse', response.json, viewers, channels);
            }
        }).get();
    });

    /**
     * Port to get top 50 streams by popularity.
     * Return a port for stream informations.
     */
    panel.port.on('printStreams', function() {
        Request ({
            url: streamAPI,
            content: {
                limit: 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('streamResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Port to get top 50 streams by game. This port is used for second page of the game tab.
     * Return a port for stream informations.
     */
    panel.port.on('getStreamsByGame', function(gameName) {
        Request ({
            url: streamAPI,
            content: {
                game : gameName,
                limit : 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('streamByGameResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Port to get top 50 featured streams.
     * Return a port for stream informations.
     */
    panel.port.on('getFeatured', function() {
        Request ({
            url: featuredAPI,
            content: {
                limit: 50
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('featuredResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Port to get search result by query.
     * Return a port for search result
     */
    panel.port.on('search', function(query) {
        Request ({
            url: searchAPI,
            content: {
                q: encodeURIComponent(query)
            },
            headers: {
                'Client-ID': CLIENT_ID,
                'Cache-control': 'no-cache'
            },
            onComplete: function(response) {
                panel.port.emit('searchResponse', response.json, viewers, playing);
            }
        }).get();
    });

    /**
     * Call initializeNotify() for initialize notifications.
     */
    panel.port.on('initialize', function() {
        initializeNotify();
    });

    /**
     * Get followers and initialize notifications.
     * Notifications send in every 5 minutes.
     */
    function initializeNotify() {
        var username = settings.getStorageItem('twitchName');

        if (username != undefined && username != '') {
            followingCheck(username, function() {
                // Firstly we are initializing a postnotify and then
                // setting it to do that for 5mins(300000 ms) repeatedly.
                if(settings.getStorageItem('notification') == true) {
                    postNotify();
                    intervalID = tmr.setInterval(postNotify, 300000); // 5 * 60 * 100
                }
            });
        }
    }

    /**
     * Send API request to get all followers.
     * Used in initialize port and for refresh the followers.
     */
    function followingCheck(username, callback) {
        if (username != undefined) {
            var followUrl = strFormat(followingAPI, encodeURIComponent(username));

            Request ({
                url: followUrl,
                content: {
                    limit: 500
                },
                headers: {
                    'Client-ID': CLIENT_ID,
                    'Cache-control': 'no-cache'
                },
                onComplete: function(response) {
                    if (response.json.follows != undefined) {
                        for (var i = 0; i < response.json.follows.length; i++) {
                            followingNames[i] = response.json.follows[i].channel.name;
                        }

                        // If callback parameter is filled, execute it.
                        if (callback && typeof(callback) === 'function') {
                            callback();
                        }
                    }
                }
            }).get();
        };
    }

    /**
     * Execute in every 5 minutes and check for the new online streamers.
     * If there is online streamer then send it to notify function to show notify.
     */
    function postNotify() {
        var streamersText = '';
        var isLiveFollowingsEmpty = liveFollowings.length == 0;
        var streamUrl = '';

        if (followingNames.length > 0) {
            var followingNamesList = followingNames.join(',');

            Request ({
                url: streamAPI,
                headers: {
                    'Client-ID': CLIENT_ID,
                    'Cache-control': 'no-cache'
                },
                content: {
                    'channel': encodeURIComponent(followingNamesList),
                    'limit': followingNames.length
                },
                onComplete: function(response) {
                    var streams = response.json.streams;
                    var streamsLength = streams.length;

                    // Checking if there is online stream in followed channels.
                    if(streamsLength > 0) {
                        // If it is first notification then go to here.
                        if (isLiveFollowingsEmpty) {
                            for(var i = 0; i < streamsLength; i++) {
                                if (streams[i].channel.display_name !== undefined) {
                                    liveFollowings.push(streams[i].channel.display_name);
                                }
                            }

                            // Setting stream url to notification url if just one channel go online
                            if(streamsLength == 1) {
                                streamUrl = streams[0].channel.url;
                            }

                            streamersText = liveFollowings.join(', ') + ' ' + _('now_online');
                            notify(streamersText, streamsLength, false, streamUrl);
                        } else {
                            // If this is not the first notification then go to here.
                            var newUniqueStreams = [];
                            for(var i = 0; i < streamsLength; i++) {
                                if (streams[i].channel.display_name !== undefined) {
                                    newLiveFollowings.push(streams[i].channel.display_name);

                                    // Checking if the streamer is not online in previous notification.
                                    if (!include(liveFollowings, streams[i].channel.display_name)) {
                                        newUniqueStreams.push(streams[i].channel.display_name);
                                    }
                                }
                            }

                            // Setting stream url to notification url if just one channel go online
                            if(newUniqueStreams.length == 1) {
                                if (!include(liveFollowings, streams[0].channel.display_name))
                                    streamUrl = streams[0].channel.url;
                            }

                            streamersText = newUniqueStreams.join(', ') + ' ' + _('now_online');
                            notify(streamersText, newUniqueStreams.length, true, streamUrl);
                        }
                    }
                }
            }).get();
        }

    }

    /**
     * Get notify text count and show notification with these.
     * If there is one online streamer then show different
     */
    function notify(ntext, count, newLive, lastUrl) {
        if (count == 1) {
            notifications.notify({
                title: ntext,
                text: _('click_to_go'),
                iconURL: './icons/twitch-icon-128.png',
                onClick: function() {
                    openLink(lastUrl, true);
                }
            });
        } else if (count > 1) {
            notifications.notify({
                title: count + ' ' + _('streamers_online'),
                text: ntext,
                iconURL: './icons/twitch-icon-128.png',
                onClick: function() {
                    openLink('https://www.twitch.tv/directory/following', false);
                }
            });
        }

        // Play audio if OS is Windows and we have actual channels gone live, if count < 1 this method causes no notification yet sound would still play
        if (platform == 'winnt' && settings.getStorageItem('notifySound') == true && count >= 1) {
            panel.port.emit('playSound', settings.getStorageItem('notifySoundRing'));
        }

        // If newlivefollowings is not empty, pass it's content to main array and empty it.
        if (newLive) {
            liveFollowings = [];
            liveFollowings = newLiveFollowings;
            newLiveFollowings = [];
        }
    }

    /**
     * Port to call clearTimer function for clear timer.
     */
    panel.port.on('clearTimer', function() {
        clearTimer();
    });

    /**
     * Clear timer and empty the follower lists.
     */
    function clearTimer() {
        tmr.clearInterval(intervalID);
        liveFollowings = [];
        newLiveFollowings = [];
    }

    /**
     * Port for login page localization.
     */
    panel.port.on('loginPage', function() {
        var locale = {
            signIn: signIn,
            passSign: _('pass_sign'),
            welcomeMsg: _('welcome_msg'),
            username: _('username')
        };

        panel.port.emit('loginPageResponse', locale);
    });

    /**
     * Port for sign in/out localization.
     */
    panel.port.on('getSignInOut', function() {

        if (settings.getStorageItem('twitchName') == undefined) {
            panel.port.emit('getSignInOutResponse', signIn);
        } else {
            panel.port.emit('getSignInOutResponse', signOut);
        }

    });

    /**
     * Port for sign out localization.
     */
    panel.port.on('getLoadCheck', function() {
        panel.port.emit('getLoadCheckResponse', signOut);
    });

    /**
     * Port for search page beginning message localization.
     */
    panel.port.on('searchMessage', function() {
        panel.port.emit('searchMessageResponse', searchMessage);
    });

    /**
     * Port for setting localizations and values.
     */
    panel.port.on('getSettings', function(isLogged) {
        var settingsLocale = {
            refreshBtn: _('refresh_fl_btn'),
            refreshMsg: _('refresh_followed'),
            help: _('donate'),
            createdBy: _('created_by'),
            notification: settings.getStorageItem('notification'),
            notifySound: settings.getStorageItem('notifySound'),
            open: settings.getStorageItem('open'),
            notifySoundRing: settings.getStorageItem('notifySoundRing'),
            showNotifications: _('show_notifications'),
            playSound: _('play_sound'),
            openStreams: _('open_streams'),
            newTab: _('new_tab'),
            newWindow: _('new_window'),
            popout: _('popout'),
            notificationSound: _('new_tab'),
            newTab: _('new_tab'),
            notificationSound: _('notification_sound')
        };

        if (isLogged) {
            settingsLocale.logMsg = _('logged_as');
            settingsLocale.logInOut = signOut;
            panel.port.emit('settingsResponse', settingsLocale);
        } else {
            settingsLocale.logMsg = _('not_logged_yet');
            settingsLocale.logInOut = signIn;
            panel.port.emit('settingsResponse', settingsLocale);
        }
    });

    panel.port.on('changeSetting', function (setting, value) {
        settings.setStorageItem(setting, value);

        if (setting == 'notification') {
            if (value == true) {
                initializeNotify();
            } else {
                clearTimer();
            }
        }
    });

    /**
     * Port for refresh followings.
     */
    panel.port.on('refreshFollowings', function() {
        followingCheck(settings.getStorageItem('twitchName'));
    });

    /**
     * Port for Open Url and hide the panel.
     */
    panel.port.on('openPage', function(url) {
        openLink(url, true)
    });

    /**
     * Open Link depend on which open type selected in settings.
     */
    function openLink(url, isStream) {
        var openType = settings.getStorageItem('open');

        if (openType == 2) {
            tabs.open({
              url: url,
              inNewWindow: true
            });
        } else if (openType == 3 && isStream) {
            url = url + '/popout';
            tabs.open(url);
        } else {
            tabs.open(url);
        }

        panel.hide();
    }

    // Set Simple Storage Item
    panel.port.on('setStorage', function(key, value) {
        settings.setStorageItem(key, value);
    });

    // Get Simple Storage Item
    panel.port.on('getStorage', function(key) {
        panel.port.emit('getStorageResponse', settings.getStorageItem(key));
    });

    // Delete Simple Storage Item
    panel.port.on('deleteStorage', function(key) {
        settings.deleteStorageItem(key)
    });
};
<|endoftext|>