<commit_before>'use strict';

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

const bitfinex = require ('./bitfinex.js');
const { ExchangeError, NotSupported, InsufficientFunds } = require ('./base/errors');

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

module.exports = class bitfinex2 extends bitfinex {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitfinex2',
            'name': 'Bitfinex v2',
            'countries': 'VG',
            'version': 'v2',
            // new metainfo interface
            'has': {
                'CORS': true,
                'createOrder': false,
                'fetchMyTrades': false,
                'fetchOHLCV': true,
                'fetchTickers': true,
                'fetchOrder': true,
                'fetchOpenOrders': false,
                'fetchClosedOrders': false,
                'withdraw': true,
                'deposit': false,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '3h': '3h',
                '6h': '6h',
                '12h': '12h',
                '1d': '1D',
                '1w': '7D',
                '2w': '14D',
                '1M': '1M',
            },
            'rateLimit': 1500,
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg',
                'api': 'https://api.bitfinex.com',
                'www': 'https://www.bitfinex.com',
                'doc': [
                    'https://bitfinex.readme.io/v2/docs',
                    'https://github.com/bitfinexcom/bitfinex-api-node',
                ],
                'fees': 'https://www.bitfinex.com/fees',
            },
            'api': {
                'v1': {
                    'get': [
                        'symbols',
                        'symbols_details',
                    ],
                },
                'public': {
                    'get': [
                        'platform/status',
                        'tickers',
                        'ticker/{symbol}',
                        'trades/{symbol}/hist',
                        'book/{symbol}/{precision}',
                        'book/{symbol}/P0',
                        'book/{symbol}/P1',
                        'book/{symbol}/P2',
                        'book/{symbol}/P3',
                        'book/{symbol}/R0',
                        'stats1/{key}:{size}:{symbol}/{side}/{section}',
                        'stats1/{key}:{size}:{symbol}/long/last',
                        'stats1/{key}:{size}:{symbol}/long/hist',
                        'stats1/{key}:{size}:{symbol}/short/last',
                        'stats1/{key}:{size}:{symbol}/short/hist',
                        'candles/trade:{timeframe}:{symbol}/{section}',
                        'candles/trade:{timeframe}:{symbol}/last',
                        'candles/trade:{timeframe}:{symbol}/hist',
                    ],
                    'post': [
                        'calc/trade/avg',
                    ],
                },
                'private': {
                    'post': [
                        'auth/r/wallets',
                        'auth/r/orders/{symbol}',
                        'auth/r/orders/{symbol}/new',
                        'auth/r/orders/{symbol}/hist',
                        'auth/r/order/{symbol}:{id}/trades',
                        'auth/r/trades/{symbol}/hist',
                        'auth/r/positions',
                        'auth/r/funding/offers/{symbol}',
                        'auth/r/funding/offers/{symbol}/hist',
                        'auth/r/funding/loans/{symbol}',
                        'auth/r/funding/loans/{symbol}/hist',
                        'auth/r/funding/credits/{symbol}',
                        'auth/r/funding/credits/{symbol}/hist',
                        'auth/r/funding/trades/{symbol}/hist',
                        'auth/r/info/margin/{key}',
                        'auth/r/info/funding/{key}',
                        'auth/r/movements/{currency}/hist',
                        'auth/r/stats/perf:{timeframe}/hist',
                        'auth/r/alerts',
                        'auth/w/alert/set',
                        'auth/w/alert/{type}:{symbol}:{price}/del',
                        'auth/calc/order/avail',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.1 / 100,
                    'taker': 0.2 / 100,
                },
                'funding': {
                    'withdraw': {
                        'BTC': 0.0005,
                        'BCH': 0.0005,
                        'ETH': 0.01,
                        'EOS': 0.1,
                        'LTC': 0.001,
                        'OMG': 0.1,
                        'IOT': 0.0,
                        'NEO': 0.0,
                        'ETC': 0.01,
                        'XRP': 0.02,
                        'ETP': 0.01,
                        'ZEC': 0.001,
                        'BTG': 0.0,
                        'DASH': 0.01,
                        'XMR': 0.04,
                        'QTM': 0.01,
                        'EDO': 0.5,
                        'DAT': 1.0,
                        'AVT': 0.5,
                        'SAN': 0.1,
                        'USDT': 5.0,
                        'SPK': 9.2784,
                        'BAT': 9.0883,
                        'GNT': 8.2881,
                        'SNT': 14.303,
                        'QASH': 3.2428,
                        'YYW': 18.055,
                    },
                },
            },
        });
    }

    commonCurrencyCode (currency) {
        const currencies = {
            'DSH': 'DASH', // Bitfinex names Dash as DSH, instead of DASH
            'QTM': 'QTUM',
            'IOT': 'IOTA',
            'DAT': 'DATA',
        };
        return (currency in currencies) ? currencies[currency] : currency;
    }

    isFiat (code) {
        let fiat = {
            'USD': 'USD',
            'EUR': 'EUR',
        };
        return (code in fiat);
    }

    getCurrencyId (code) {
        let isFiat = this.isFiat (code);
        let prefix = isFiat ? 'f' : 't';
        return prefix + code;
    }

    async fetchMarkets () {
        let markets = await this.v1GetSymbolsDetails ();
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let market = markets[p];
            let id = market['pair'].toUpperCase ();
            let baseId = id.slice (0, 3);
            let quoteId = id.slice (3, 6);
            let base = this.commonCurrencyCode (baseId);
            let quote = this.commonCurrencyCode (quoteId);
            let symbol = base + '/' + quote;
            id = 't' + id;
            baseId = this.getCurrencyId (baseId);
            quoteId = this.getCurrencyId (quoteId);
            let precision = {
                'price': market['price_precision'],
                'amount': market['price_precision'],
            };
            let limits = {
                'amount': {
                    'min': parseFloat (market['minimum_order_size']),
                    'max': parseFloat (market['maximum_order_size']),
                },
                'price': {
                    'min': Math.pow (10, -precision['price']),
                    'max': Math.pow (10, precision['price']),
                },
            };
            limits['cost'] = {
                'min': limits['amount']['min'] * limits['price']['min'],
                'max': undefined,
            };
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': true,
                'precision': precision,
                'limits': limits,
                'lot': Math.pow (10, -precision['amount']),
                'info': market,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        let response = await this.privatePostAuthRWallets ();
        let balanceType = this.safeString (params, 'type', 'exchange');
        let result = { 'info': response };
        for (let b = 0; b < response.length; b++) {
            let balance = response[b];
            let accountType = balance[0];
            let currency = balance[1];
            let total = balance[2];
            let available = balance[4];
            if (accountType === balanceType) {
                if (currency[0] === 't')
                    currency = currency.slice (1);
                let uppercase = currency.toUpperCase ();
                uppercase = this.commonCurrencyCode (uppercase);
                let account = this.account ();
                account['free'] = available;
                account['total'] = total;
                if (account['free'])
                    account['used'] = account['total'] - account['free'];
                result[uppercase] = account;
            }
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        let orderbook = await this.publicGetBookSymbolPrecision (this.extend ({
            'symbol': this.marketId (symbol),
            'precision': 'R0',
        }, params));
        let timestamp = this.milliseconds ();
        let result = {
            'bids': [],
            'asks': [],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
        };
        for (let i = 0; i < orderbook.length; i++) {
            let order = orderbook[i];
            let price = order[1];
            let amount = order[2];
            let side = (amount > 0) ? 'bids' : 'asks';
            amount = Math.abs (amount);
            result[side].push ([ price, amount ]);
        }
        result['bids'] = this.sortBy (result['bids'], 0, true);
        result['asks'] = this.sortBy (result['asks'], 0);
        return result;
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.milliseconds ();
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let length = ticker.length;
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': ticker[length - 2],
            'low': ticker[length - 1],
            'bid': ticker[length - 10],
            'ask': ticker[length - 8],
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': ticker[length - 4],
            'change': ticker[length - 6],
            'percentage': ticker[length - 5],
            'average': undefined,
            'baseVolume': ticker[length - 3],
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        let tickers = await this.publicGetTickers (this.extend ({
            'symbols': this.ids.join (','),
        }, params));
        let result = {};
        for (let i = 0; i < tickers.length; i++) {
            let ticker = tickers[i];
            let id = ticker[0];
            let market = this.markets_by_id[id];
            let symbol = market['symbol'];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        let market = this.markets[symbol];
        let ticker = await this.publicGetTickerSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market) {
        let [ id, timestamp, amount, price ] = trade;
        let side = (amount < 0) ? 'sell' : 'buy';
        if (amount < 0) {
            amount = -amount;
        }
        return {
            'id': id.toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': side,
            'price': price,
            'amount': amount,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = 120, params = {}) {
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
            'sort': 1,
            'limit': limit, // default = max = 120
        };
        if (typeof since !== 'undefined')
            request['start'] = since;
        let response = await this.publicGetTradesSymbolHist (this.extend (request, params));
        let trades = this.sortBy (response, 1);
        return this.parseTrades (trades, market, undefined, limit);
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 100, params = {}) {
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
            'timeframe': this.timeframes[timeframe],
            'sort': 1,
            'limit': limit,
        };
        if (typeof since !== 'undefined')
            request['start'] = since;
        request = this.extend (request, params);
        let response = await this.publicGetCandlesTradeTimeframeSymbolHist (request);
        return this.parseOHLCVs (response, market, timeframe, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        throw new NotSupported (this.id + ' createOrder not implemented yet');
    }

    cancelOrder (id, symbol = undefined, params = {}) {
        throw new NotSupported (this.id + ' cancelOrder not implemented yet');
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        throw new NotSupported (this.id + ' fetchOrder not implemented yet');
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        throw new NotSupported (this.id + ' withdraw not implemented yet');
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = 25, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
            // 'limit': limit,
            // 'end': this.milliseconds (),
        };
        // if (typeof since !== 'undefined')
            // request['start'] = since; // parseInt (since / 1000);
        let response = await this.privatePostAuthRTradesSymbolHist (this.extend (request, params));
        const log = require ('ololog');
        log (response);
        return this.parseTrades (response, market, since, limit);
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let request = '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'v1')
            request = api + request;
        else
            request = this.version + request;
        let url = this.urls['api'] + '/' + request;
        if (api === 'public') {
            if (Object.keys (query).length) {
                url += '?' + this.urlencode (query);
            }
        }
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            body = this.json (query);
            let auth = '/api' + '/' + request + nonce + body;
            let signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha384');
            headers = {
                'bfx-nonce': nonce,
                'bfx-apikey': this.apiKey,
                'bfx-signature': signature,
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if (response) {
            if ('message' in response) {
                if (response['message'].indexOf ('not enough exchange balance') >= 0)
                    throw new InsufficientFunds (this.id + ' ' + this.json (response));
                throw new ExchangeError (this.id + ' ' + this.json (response));
            }
            return response;
        } else if (response === '') {
            throw new ExchangeError (this.id + ' returned empty response');
        }
        return response;
    }
};
<commit_msg>bitfinex2 fetchMyTrades<commit_after>'use strict';

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

const bitfinex = require ('./bitfinex.js');
const { ExchangeError, NotSupported, InsufficientFunds } = require ('./base/errors');

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

module.exports = class bitfinex2 extends bitfinex {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitfinex2',
            'name': 'Bitfinex v2',
            'countries': 'VG',
            'version': 'v2',
            // new metainfo interface
            'has': {
                'CORS': true,
                'createOrder': false,
                'fetchMyTrades': false,
                'fetchOHLCV': true,
                'fetchTickers': true,
                'fetchOrder': true,
                'fetchOpenOrders': false,
                'fetchClosedOrders': false,
                'withdraw': true,
                'deposit': false,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '3h': '3h',
                '6h': '6h',
                '12h': '12h',
                '1d': '1D',
                '1w': '7D',
                '2w': '14D',
                '1M': '1M',
            },
            'rateLimit': 1500,
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg',
                'api': 'https://api.bitfinex.com',
                'www': 'https://www.bitfinex.com',
                'doc': [
                    'https://bitfinex.readme.io/v2/docs',
                    'https://github.com/bitfinexcom/bitfinex-api-node',
                ],
                'fees': 'https://www.bitfinex.com/fees',
            },
            'api': {
                'v1': {
                    'get': [
                        'symbols',
                        'symbols_details',
                    ],
                },
                'public': {
                    'get': [
                        'platform/status',
                        'tickers',
                        'ticker/{symbol}',
                        'trades/{symbol}/hist',
                        'book/{symbol}/{precision}',
                        'book/{symbol}/P0',
                        'book/{symbol}/P1',
                        'book/{symbol}/P2',
                        'book/{symbol}/P3',
                        'book/{symbol}/R0',
                        'stats1/{key}:{size}:{symbol}/{side}/{section}',
                        'stats1/{key}:{size}:{symbol}/long/last',
                        'stats1/{key}:{size}:{symbol}/long/hist',
                        'stats1/{key}:{size}:{symbol}/short/last',
                        'stats1/{key}:{size}:{symbol}/short/hist',
                        'candles/trade:{timeframe}:{symbol}/{section}',
                        'candles/trade:{timeframe}:{symbol}/last',
                        'candles/trade:{timeframe}:{symbol}/hist',
                    ],
                    'post': [
                        'calc/trade/avg',
                    ],
                },
                'private': {
                    'post': [
                        'auth/r/wallets',
                        'auth/r/orders/{symbol}',
                        'auth/r/orders/{symbol}/new',
                        'auth/r/orders/{symbol}/hist',
                        'auth/r/order/{symbol}:{id}/trades',
                        'auth/r/trades/{symbol}/hist',
                        'auth/r/positions',
                        'auth/r/funding/offers/{symbol}',
                        'auth/r/funding/offers/{symbol}/hist',
                        'auth/r/funding/loans/{symbol}',
                        'auth/r/funding/loans/{symbol}/hist',
                        'auth/r/funding/credits/{symbol}',
                        'auth/r/funding/credits/{symbol}/hist',
                        'auth/r/funding/trades/{symbol}/hist',
                        'auth/r/info/margin/{key}',
                        'auth/r/info/funding/{key}',
                        'auth/r/movements/{currency}/hist',
                        'auth/r/stats/perf:{timeframe}/hist',
                        'auth/r/alerts',
                        'auth/w/alert/set',
                        'auth/w/alert/{type}:{symbol}:{price}/del',
                        'auth/calc/order/avail',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.1 / 100,
                    'taker': 0.2 / 100,
                },
                'funding': {
                    'withdraw': {
                        'BTC': 0.0005,
                        'BCH': 0.0005,
                        'ETH': 0.01,
                        'EOS': 0.1,
                        'LTC': 0.001,
                        'OMG': 0.1,
                        'IOT': 0.0,
                        'NEO': 0.0,
                        'ETC': 0.01,
                        'XRP': 0.02,
                        'ETP': 0.01,
                        'ZEC': 0.001,
                        'BTG': 0.0,
                        'DASH': 0.01,
                        'XMR': 0.04,
                        'QTM': 0.01,
                        'EDO': 0.5,
                        'DAT': 1.0,
                        'AVT': 0.5,
                        'SAN': 0.1,
                        'USDT': 5.0,
                        'SPK': 9.2784,
                        'BAT': 9.0883,
                        'GNT': 8.2881,
                        'SNT': 14.303,
                        'QASH': 3.2428,
                        'YYW': 18.055,
                    },
                },
            },
        });
    }

    commonCurrencyCode (currency) {
        const currencies = {
            'DSH': 'DASH', // Bitfinex names Dash as DSH, instead of DASH
            'QTM': 'QTUM',
            'IOT': 'IOTA',
            'DAT': 'DATA',
        };
        return (currency in currencies) ? currencies[currency] : currency;
    }

    isFiat (code) {
        let fiat = {
            'USD': 'USD',
            'EUR': 'EUR',
        };
        return (code in fiat);
    }

    getCurrencyId (code) {
        let isFiat = this.isFiat (code);
        let prefix = isFiat ? 'f' : 't';
        return prefix + code;
    }

    async fetchMarkets () {
        let markets = await this.v1GetSymbolsDetails ();
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let market = markets[p];
            let id = market['pair'].toUpperCase ();
            let baseId = id.slice (0, 3);
            let quoteId = id.slice (3, 6);
            let base = this.commonCurrencyCode (baseId);
            let quote = this.commonCurrencyCode (quoteId);
            let symbol = base + '/' + quote;
            id = 't' + id;
            baseId = this.getCurrencyId (baseId);
            quoteId = this.getCurrencyId (quoteId);
            let precision = {
                'price': market['price_precision'],
                'amount': market['price_precision'],
            };
            let limits = {
                'amount': {
                    'min': parseFloat (market['minimum_order_size']),
                    'max': parseFloat (market['maximum_order_size']),
                },
                'price': {
                    'min': Math.pow (10, -precision['price']),
                    'max': Math.pow (10, precision['price']),
                },
            };
            limits['cost'] = {
                'min': limits['amount']['min'] * limits['price']['min'],
                'max': undefined,
            };
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': true,
                'precision': precision,
                'limits': limits,
                'lot': Math.pow (10, -precision['amount']),
                'info': market,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        let response = await this.privatePostAuthRWallets ();
        let balanceType = this.safeString (params, 'type', 'exchange');
        let result = { 'info': response };
        for (let b = 0; b < response.length; b++) {
            let balance = response[b];
            let accountType = balance[0];
            let currency = balance[1];
            let total = balance[2];
            let available = balance[4];
            if (accountType === balanceType) {
                if (currency[0] === 't')
                    currency = currency.slice (1);
                let uppercase = currency.toUpperCase ();
                uppercase = this.commonCurrencyCode (uppercase);
                let account = this.account ();
                account['free'] = available;
                account['total'] = total;
                if (account['free'])
                    account['used'] = account['total'] - account['free'];
                result[uppercase] = account;
            }
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        let orderbook = await this.publicGetBookSymbolPrecision (this.extend ({
            'symbol': this.marketId (symbol),
            'precision': 'R0',
        }, params));
        let timestamp = this.milliseconds ();
        let result = {
            'bids': [],
            'asks': [],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
        };
        for (let i = 0; i < orderbook.length; i++) {
            let order = orderbook[i];
            let price = order[1];
            let amount = order[2];
            let side = (amount > 0) ? 'bids' : 'asks';
            amount = Math.abs (amount);
            result[side].push ([ price, amount ]);
        }
        result['bids'] = this.sortBy (result['bids'], 0, true);
        result['asks'] = this.sortBy (result['asks'], 0);
        return result;
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.milliseconds ();
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let length = ticker.length;
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': ticker[length - 2],
            'low': ticker[length - 1],
            'bid': ticker[length - 10],
            'ask': ticker[length - 8],
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': ticker[length - 4],
            'change': ticker[length - 6],
            'percentage': ticker[length - 5],
            'average': undefined,
            'baseVolume': ticker[length - 3],
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        let tickers = await this.publicGetTickers (this.extend ({
            'symbols': this.ids.join (','),
        }, params));
        let result = {};
        for (let i = 0; i < tickers.length; i++) {
            let ticker = tickers[i];
            let id = ticker[0];
            let market = this.markets_by_id[id];
            let symbol = market['symbol'];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        let market = this.markets[symbol];
        let ticker = await this.publicGetTickerSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market) {
        let [ id, timestamp, amount, price ] = trade;
        let side = (amount < 0) ? 'sell' : 'buy';
        if (amount < 0) {
            amount = -amount;
        }
        return {
            'id': id.toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': side,
            'price': price,
            'amount': amount,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = 120, params = {}) {
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
            'sort': 1,
            'limit': limit, // default = max = 120
        };
        if (typeof since !== 'undefined')
            request['start'] = since;
        let response = await this.publicGetTradesSymbolHist (this.extend (request, params));
        let trades = this.sortBy (response, 1);
        return this.parseTrades (trades, market, undefined, limit);
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = 100, params = {}) {
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
            'timeframe': this.timeframes[timeframe],
            'sort': 1,
            'limit': limit,
        };
        if (typeof since !== 'undefined')
            request['start'] = since;
        request = this.extend (request, params);
        let response = await this.publicGetCandlesTradeTimeframeSymbolHist (request);
        return this.parseOHLCVs (response, market, timeframe, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        throw new NotSupported (this.id + ' createOrder not implemented yet');
    }

    cancelOrder (id, symbol = undefined, params = {}) {
        throw new NotSupported (this.id + ' cancelOrder not implemented yet');
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        throw new NotSupported (this.id + ' fetchOrder not implemented yet');
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        throw new NotSupported (this.id + ' withdraw not implemented yet');
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = 25, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let request = {
            'symbol': market['id'],
            'limit': limit,
            'end': this.seconds (),
        };
        if (typeof since !== 'undefined')
            request['start'] = parseInt (since / 1000);
        let response = await this.privatePostAuthRTradesSymbolHist (this.extend (request, params));
        // return this.parseTrades (response, market, since, limit); // not implemented yet for bitfinex v2
        return response;
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let request = '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'v1')
            request = api + request;
        else
            request = this.version + request;
        let url = this.urls['api'] + '/' + request;
        if (api === 'public') {
            if (Object.keys (query).length) {
                url += '?' + this.urlencode (query);
            }
        }
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            body = this.json (query);
            let auth = '/api' + '/' + request + nonce + body;
            let signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha384');
            headers = {
                'bfx-nonce': nonce,
                'bfx-apikey': this.apiKey,
                'bfx-signature': signature,
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if (response) {
            if ('message' in response) {
                if (response['message'].indexOf ('not enough exchange balance') >= 0)
                    throw new InsufficientFunds (this.id + ' ' + this.json (response));
                throw new ExchangeError (this.id + ' ' + this.json (response));
            }
            return response;
        } else if (response === '') {
            throw new ExchangeError (this.id + ' returned empty response');
        }
        return response;
    }
};
<|endoftext|>


<commit_before>/* global TrelloPowerUp */

var t = TrelloPowerUp.iframe();
var firebaseRef;
var cards = [];
var milestones = [];
var matchedData = {};
var canvasElement = document.getElementById("result");
var dayGridElement = document.getElementById("day-grid");
var scroller = document.getElementById('scroller');
var content = document.getElementById('content');
var dayWidth = 0;
var today = new Date();
var shortMonth = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

t.render(function(){

  firebaseRef = firebase.database().ref('cards/');
  firebaseRef.on('value', function(snapshot) {
    milestones = snapshot.val();
    dataMatch();
  });

  return t.cards('id', 'name').
    then(function(promiseResult) {
      cards = promiseResult;
      dataMatch();
    });

});

//data match
var dataMatch = function() {

  if(milestones.length !== 0 && cards.length !== 0) {

    console.log("Starting to card match");

    cards.forEach(function(card) {

      if(milestones[card.id]) {
        matchedData[card.id] = {
          milestone: milestones[card.id],
          card: card
        };
      }

    });

    console.log("Matched data to render");
    render();

  }

};

var calculateFrame = function() {

  var barHeight = 30;
  var height = ((Object.keys(matchedData).length + 1) * (30 + 8)) - 8;
  canvasElement.style.height = height + "px";
  dayWidth = (window.innerWidth - 16) / 30;

  scroller.style.width = (dayWidth * 51) + "px";
  dayGridElement.style.width = (dayWidth * 51) + "px";
  content.scrollLeft = (dayWidth * 7 * -1);

};

var render = function() {
  console.log("Matched Data", matchedData);
  calculateFrame();
  renderCalendar();
  Object.keys(matchedData).forEach(function(cardId) {
    renderBar(cardId, matchedData[cardId]);
  });
};

var renderCalendar = function() {
  for(var i = -7; i < 44; i++) {
    var dayEl = document.createElement("div");
    dayEl.className = "day-slot";
    dayEl.style.width = (dayWidth) + "px";
    dayEl.style.height = "100%";
    var result = new Date();
    result.setDate(result.getDate() + i);
    dayEl.innerHTML = result.getDate() + ' ' + shortMonth[result.getMonth()];
    dayGridElement.appendChild(dayEl);
  }
};

var renderBar = function(cardId, barObj) {

  var getBar = document.getElementById("milestone-bar-" + cardId);

  if(!getBar) {
    getBar = document.createElement("div");
    getBar.id = "milestone-bar-" + cardId;
    getBar.className = "milestone-bar";
    canvasElement.appendChild(getBar);
  }

  getBar.innerHTML = barObj.card.name;

  //get earliest date
  //get last date

  var milestones = barObj.milestone.milestones;
  var min = Infinity, max = -Infinity, x;
  for( x in milestones) {
      var d = new Date(milestones[x].date);
      if( d < min) min = d;
      if( d > max) max = d;
  }

  console.log("Milestone, min max", min, max);

  var daysBetweenMin = days_between(min, today);
  var daysBetweenMax = days_between(max, today);
  var daysBetweenBoth = days_between(max, min);
  console.log("days between both", daysBetweenBoth);
  var dayWidth = (window.innerWidth - 16) / 30;

  getBar.style.width = (dayWidth * daysBetweenBoth) + "px";
  getBar.style.left = (dayWidth * daysBetweenMin) + "px";

  console.log("Days between", daysBetweenMin, daysBetweenMax);

  //render milesstones in the bar
  for(x in milestones) {
    console.log('draw milestone');
    var d = new Date(milestones[x].date);
    var fromStart = days_between(d, min);
    var dot = document.createElement("div");
    dot.className = "milestone";
    getBar.appendChild(dot);
    dot.style.left = ((fromStart * dayWidth) - 5) + "px";
    console.log(d, fromStart, dot, dot.style.left);
  }

};

function days_between(date1, date2) {

    // The number of milliseconds in one day
    var ONE_DAY = 1000 * 60 * 60 * 24

    // Convert both dates to milliseconds
    var date1_ms = date1.getTime()
    var date2_ms = date2.getTime()

    // Calculate the difference in milliseconds
    var difference_ms = date1_ms - date2_ms;

    // Convert back to days and return
    return Math.round(difference_ms/ONE_DAY)

}

<commit_msg>moar width<commit_after>/* global TrelloPowerUp */

var t = TrelloPowerUp.iframe();
var firebaseRef;
var cards = [];
var milestones = [];
var matchedData = {};
var canvasElement = document.getElementById("result");
var dayGridElement = document.getElementById("day-grid");
var scroller = document.getElementById('scroller');
var content = document.getElementById('content');
var dayWidth = 0;
var today = new Date();
var shortMonth = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

t.render(function(){

  firebaseRef = firebase.database().ref('cards/');
  firebaseRef.on('value', function(snapshot) {
    milestones = snapshot.val();
    dataMatch();
  });

  return t.cards('id', 'name').
    then(function(promiseResult) {
      cards = promiseResult;
      dataMatch();
    });

});

//data match
var dataMatch = function() {

  if(milestones.length !== 0 && cards.length !== 0) {

    console.log("Starting to card match");

    cards.forEach(function(card) {

      if(milestones[card.id]) {
        matchedData[card.id] = {
          milestone: milestones[card.id],
          card: card
        };
      }

    });

    console.log("Matched data to render");
    render();

  }

};

var calculateFrame = function() {

  var barHeight = 30;
  var height = ((Object.keys(matchedData).length + 1) * (30 + 8)) - 8;
  canvasElement.style.height = height + "px";
  dayWidth = (window.innerWidth - 16) / 30;

  scroller.style.width = (dayWidth * 51) + "px";
  dayGridElement.style.width = (dayWidth * 51) + "px";
  content.scrollLeft = (dayWidth * 7 * -1);
  canvasElement.style.width = (dayWidth * 51) + "px";

};

var render = function() {
  console.log("Matched Data", matchedData);
  calculateFrame();
  renderCalendar();
  Object.keys(matchedData).forEach(function(cardId) {
    renderBar(cardId, matchedData[cardId]);
  });
};

var renderCalendar = function() {
  for(var i = -7; i < 44; i++) {
    var dayEl = document.createElement("div");
    dayEl.className = "day-slot";
    dayEl.style.width = (dayWidth) + "px";
    dayEl.style.height = "100%";
    var result = new Date();
    result.setDate(result.getDate() + i);
    dayEl.innerHTML = result.getDate() + ' ' + shortMonth[result.getMonth()];
    dayGridElement.appendChild(dayEl);
  }
};

var renderBar = function(cardId, barObj) {

  var getBar = document.getElementById("milestone-bar-" + cardId);

  if(!getBar) {
    getBar = document.createElement("div");
    getBar.id = "milestone-bar-" + cardId;
    getBar.className = "milestone-bar";
    canvasElement.appendChild(getBar);
  }

  getBar.innerHTML = barObj.card.name;

  //get earliest date
  //get last date

  var milestones = barObj.milestone.milestones;
  var min = Infinity, max = -Infinity, x;
  for( x in milestones) {
      var d = new Date(milestones[x].date);
      if( d < min) min = d;
      if( d > max) max = d;
  }

  console.log("Milestone, min max", min, max);

  var daysBetweenMin = days_between(min, today);
  var daysBetweenMax = days_between(max, today);
  var daysBetweenBoth = days_between(max, min);
  console.log("days between both", daysBetweenBoth);
  var dayWidth = (window.innerWidth - 16) / 30;

  getBar.style.width = (dayWidth * daysBetweenBoth) + "px";
  getBar.style.left = (dayWidth * daysBetweenMin) + "px";

  console.log("Days between", daysBetweenMin, daysBetweenMax);

  //render milesstones in the bar
  for(x in milestones) {
    console.log('draw milestone');
    var d = new Date(milestones[x].date);
    var fromStart = days_between(d, min);
    var dot = document.createElement("div");
    dot.className = "milestone";
    getBar.appendChild(dot);
    dot.style.left = ((fromStart * dayWidth) - 5) + "px";
    console.log(d, fromStart, dot, dot.style.left);
  }

};

function days_between(date1, date2) {

    // The number of milliseconds in one day
    var ONE_DAY = 1000 * 60 * 60 * 24

    // Convert both dates to milliseconds
    var date1_ms = date1.getTime()
    var date2_ms = date2.getTime()

    // Calculate the difference in milliseconds
    var difference_ms = date1_ms - date2_ms;

    // Convert back to days and return
    return Math.round(difference_ms/ONE_DAY)

}

<|endoftext|>


<commit_before>/* exported Character */

function Character(characterData, skillsData) {

	// properties
	this.id = characterData.character_list[0].character_id;
	this.name = characterData.character_list[0].name.first;
	this.rank = characterData.character_list[0].battle_rank.value;
	this.outfitRank = characterData.character_list[0].outfit_member.rank_ordinal;
	this.online = characterData.character_list[0].online_status === '1';

	// methods

	this.getCertLine = function(skillDefinition) {
		var retObj = null;
		$.each(skillsData.characters_skill_list, function(idx, skill) {
			// if there is no skill details data then skip it
			if (!skill.skill_id_join_skill) {
				return;
			}
			if (skill.skill_id_join_skill.skill_line_id === skillDefinition.skill.skillLine.toString()) {
				retObj = skill.skill_id_join_skill;
				return false;
			}
		});
		return retObj;
	};

	this.getEquipment = function(id) {
		var equipstr = 'n/a';
		$.each(characterData.character_list[0].items, function(idx, item) {
			if (item.item_id === id.toString()) {
				equipstr = item.name.en;
				return false;
			}
		});
		return equipstr;
	};

}
<commit_msg>Added lastLogin property to Character().<commit_after>/* exported Character */

function Character(characterData, skillsData) {

	// properties
	this.id = characterData.character_list[0].character_id;
	this.name = characterData.character_list[0].name.first;
	this.rank = characterData.character_list[0].battle_rank.value;
	this.outfitRank = characterData.character_list[0].outfit_member.rank_ordinal;
	this.online = characterData.character_list[0].online_status === '1';
	this.lastLogin = characterData.character_list[0].times.last_login_date;

	// methods

	this.getCertLine = function(skillDefinition) {
		var retObj = null;
		$.each(skillsData.characters_skill_list, function(idx, skill) {
			// if there is no skill details data then skip it
			if (!skill.skill_id_join_skill) {
				return;
			}
			if (skill.skill_id_join_skill.skill_line_id === skillDefinition.skill.skillLine.toString()) {
				retObj = skill.skill_id_join_skill;
				return false;
			}
		});
		return retObj;
	};

	this.getEquipment = function(id) {
		var equipstr = 'n/a';
		$.each(characterData.character_list[0].items, function(idx, item) {
			if (item.item_id === id.toString()) {
				equipstr = item.name.en;
				return false;
			}
		});
		return equipstr;
	};

}
<|endoftext|>


<commit_before>gvar home_page = $("#home_page").val()
var previous_hash_params = {}
var previous_hash_base = ""
var ghiapi = null

// ---- Show flash message ---- //
function show_flash(type, message, fade){
    $("#flash_message").fadeOut(function(){
        $("#flash_message").html("")

        var banner = 'banner_message'
        banner = banner.replace("banner_type", type).replace("banner_message", message)
        $("#flash_message").html(banner)
        $("#flash_message").fadeIn()

        if (fade === true){
            var t = setTimeout("hide_flash()", 10000)
        }

    })
}

function hide_flash(){
    $("#flash_message").fadeOut()
}

// ---- AJAX setup ---- //
var current_colorbox = null
var current_container = null
var loading = false
var colorbox_open = false

function change_hash(e, location){
    if (e.ctrlKey){
        window.open("#" + location, "_blank")
    }
    else{
        window.location.hash = location
    }
}

function bind_hashchange(){
    $(window).hashchange(function(){
        // Fires every time the hash changes!  This thing is pretty jimmy rigged.
        var hash = window.location.hash
        var parameters = {}

        if (hash !== ""){
            var location = hash.substr(1)
            var location_split = location.split("?")

            if (location_split[0] != previous_hash_base){
                // If the base page changes, trigger change_tab
                change_tab(location)
            }
            else{
                if (location_split[1]){
                    //If there are parameters, deparam them
                    parameters = $.deparam(location_split[1])
                }

                difference = diff(previous_hash_params, parameters)
                var search_change = false
                var other_change = false
                for (var key in difference){
                    // If there is a search difference, throw flag
                    if (key == "search"){
                        search_change = true
                    }
                    // If there is any other difference in the parameter, throw flag
                    else{
                        other_change = true
                    }
                }

                // If any part of the parameters changed besides search, change tab like normal
                if (other_change == true){
                    change_tab(location)
                }
                // But if search is the only thing that changed, insert query into box and fire
                else if(search_change == true){
                    var query = parameters["search"]
                    if (query == null){
                        query = ""
                    }

                    $("#search_query").val(query)
                    trigger_search(query)
                }
            }
        }

        else{
            window.location.hash = home_page
            change_tab(home_page)
        }

        //Saving these parameters for comparison next time
        if (location_split){
            previous_hash_base = location_split[0]
        }
        else{
            previous_hash_base = ""
        }

        previous_hash_params = parameters
    })
}

function change_search_hash(query){
    var hash = window.location.hash.split("?")
    if (hash[1]){
        var parameters = $.deparam(hash[1])
        parameters["search"] = query
    }
    else{
        var parameters = {"search" : query}
    }

    hash = hash[0] + "?" + $.param(parameters)
    window.location.hash = hash
}

function change_tab(location)
{
    loading = true

    $.colorbox.close()

    show_flash("setting", "Loading page...", false)

    $("#actualbody").fadeOut(300, function(){
        //To prevent page flashing before the fadeOut is finished
        $.ajax({
            url: "/ajax/" + location,
            cache: false,
            success: function(html){
                $("#actualbody").html(html);
                hide_flash()

                //If we lost the auth cookie, bounce back to login screen instead of loading that in the body
                if ($("#login_form").exists()){
                    window.location = "/login"
                }
                else {
                    $("#actualbody").fadeIn(300)
                    $("#refresh").attr("href", window.location.hash)
                }
            }
        });
    })

    loading = false
}

function setupSearchEvents(){
    $("#search_query").focus(function(){
        $("#search_help").slideDown()
    })

    $("#search_query").focusout(function(){
        $("#search_help").slideUp()
    })

    $("#search_go").click(function(){
        var query = $("#search_query").val()
        change_search_hash(query)
    })

    $("#search_query").keyup(function(e){
        if (e.keyCode == 13){
            $("#search_go").click()
        }
    })
}

// ---- DataTables ---- //
var cursors_dict = {0:null}
var data_loading = false
var current_page = 0
var data_table = null

var pub_rpc_request = null
var pub_rpc_params = null
var pub_callback = null

function initializeTable(num_columns, rpc_request, rpc_params, callback){
    //Making these variables public
    pub_rpc_params = rpc_params
    pub_rpc_request = rpc_request
    pub_callback = callback

    // Reset cursor dict
    cursors_dict = {0:null}

    var column_array = [{bVisible:false}]

    var i=0;
    for (i=0; i<num_columns; i++)
    {
        //Makes an array that DataTables likes for formatting
        column_array.push(null)
    }

    if (data_table){
        //Destroy a table if it exists
        data_table.fnDestroy()
    }

    //Set up Datatable
    data_table = $(".dataTable").dataTable({
        "sPaginationType": "two_button",
        "bJQueryUI": true,
        "bVisible" : true,
        "bDestroy": true,
        "bAutoWidth" : true,

        "oLanguage": {
            "sEmptyTable": " "
        },

        //Disable some features
        "bLengthChange": false,
        "bFilter": false,
        "bSort": false,
        "bInfo": false,
        "iDisplayLength": 15,

        "aoColumns" : column_array
    });

    $(".dataTable").css("width", "100%")

    //Setting page to 0
    current_page = 0

    //AJAX Controls
    $(".ui-corner-left").unbind("click")
    $(".ui-corner-right").unbind("click")

    $(".ui-corner-right").click(function(){
        if (data_loading == false){
            data_loading = true
            $(this).effect("highlight", {}, 3000);

            try{
               current_page += 1
               pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
            }
            catch(e){
                current_page -= 1
                pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
            }

        }
    })

    $(".ui-corner-left").click(function(){
        if (data_loading == false){
           data_loading = true
           $(this).effect("highlight", {}, 3000);

            if (current_page >= 1){
                try{
                    current_page -= 1
                    pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
                }
                catch(e){
                    current_page += 1
                    pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
                }
            }
            else{
                show_flash("setting", "There isn't any more data to show.", true)
                pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
            }
        }
    })

    pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
    return data_table
}

function pageThrough(data_table, page_number, rpc_request, rpc_params, callback){
    try{
        var query_cursor = cursors_dict[page_number]
    }
    catch(e){
        var query_cursor = "end"
    }

    if(query_cursor == "end"){
        show_flash("setting", "There isn't any more data to show.", true)
        throw "No more data"
    }

    if (query_cursor != null){
        query_cursor = query_cursor
    }

    // -- RPC to get donations -- //
    var params = {'action':rpc_request, 'query_cursor':query_cursor}

    //Adding additional parameters to params dictionary
    if (rpc_params != null){
        params = $.extend(params, rpc_params)
    }

    $("#search_icon").hide()
    $("#search_loading").show()

    var request = rpc_request(params)
    request.execute(function(response){
        $("#search_loading").hide()
        $("#search_icon").show()

        data_table.fnClearTable()

        if (response.objects){

            $.each(response.objects, function(index, d){
                callback(data_table, d)
            })
        }

        if (response.new_cursor == null){
            var new_cursor = "end"
        }
        else{
            var new_cursor = response.new_cursor
        }

        cursors_dict[page_number + 1] = new_cursor

        data_table.fnAdjustColumnSizing()

        data_loading = false
    })
}

function htmlDecode(input){
  var e = document.createElement('div');
  e.innerHTML = input;
  return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}

// ---- Colorbox ---- //
//Initialize Colorbox
function loadColorbox(url, container){
    current_colorbox = url
    current_container = container
    //console.log("Loading url: " + url + " in container: " + container)

    $.get(url, function(data) {
        $("#" + container).html(data);
        $("#" + container).css("display", "block")

        if ($("#login_page").exists()){
            window.location = "/login"
        }
        else{
            $.colorbox({inline:true, href:"#" + container, width:"80%", height:"80%"})
        }
    });
}

//Closes Colorbox
function closeColorbox(){
    $.colorbox.close()
}

function refreshPage(){
    if (colorbox_open == true){
        loadColorbox(current_colorbox, current_container)
    }
    else{
        var hash = window.location.hash
        var location = hash.substr(1)
        change_tab(location)

        cursors_dict = {0:null}
        data_loading = false
        current_page = 0
        data_table = null

        pub_rpc_request = null
        pub_rpc_params = null
        pub_callback = null
    }
}

function refreshTable(){
    pageThrough(data_table, current_page, pub_rpc_request, pub_rpc_params, pub_callback)
}

function rpcGet(data, callback){
    var url = '/rpc?' + $.param(data)
    $.getJSON(url, function(data){
        callback(data)
    })
}

function rpcPost(data, callback){
    $.ajax({type: "post",
            url: "/rpc",
            data: $.toJSON(data),
            success: function(data) {
                data = JSON.parse(data)
                if (data[0] == true){
                    show_flash("done", data[1], true)
                    callback(data)
                }
                else{

                    try{
                        message = data[1]
                        if (message == ""){
                            message = "An error occurred."
                        }
                    }
                    catch(err){
                        message = "An error occurred."
                    }

                    show_flash("undone", data[1], true)
                }
            }
        });
}

function rpcSuccessMessage(response){
    if (response.success == true){
        show_flash("done", response.message, true)
    }
    else{
        if (response.error_message){
            message = response.error_message
        }
        else{
            message = response.message
        }

        try{
            if (message == ""){
                message = "An error occurred."
            }
            else if(message == "Internal Server Error"){
                message = "Uh oh! The server's not playing nice right now. Try again later."
            }
        }
        catch(err){
            message = "An error occurred."
        }

        show_flash("undone", message, false)
        throw "Server error"
    }
}

// ---- Spreadsheet Download Controllers ---- //
function checkTaskStatus(response){
    var params2 = {"job_id":response.job_id}
    var request2 = ghiapi.spreadsheet.check(params2)

    request2.execute(function(response2){

        if (response2.completed == true){
            var url = "/ajax/spreadsheet/download?blob_key=" + response2.blob_key
            window.open(url)

            $("#download_query").html('Downloading now...')
            setTimeout(function(){
                $("#download_query").html('<button class="small button">Download query as a spreadsheet</button>')
            }, 5000)
        }
        else{
            $("#download_progress").text(response2.status)
            // If the report isn't done generating, check again later
            timeoutCheckStatus(response)
        }

    })
}

function setupDownloadQuery(mode, file_name){
    $("#download_query").delegate("button", "click", function(){
        $(this).hide()
        $("#download_query").html("Generating report - 0% finished. ")

        var query = $("#search_query").val()
        var params = {"mode":mode, "filename":file_name}
        var request = ghiapi.spreadsheet.start(params)

        request.execute(function(response){
            timeoutCheckStatus(response)
        })

    })
}

function timeoutCheckStatus(response){
    setTimeout(function(){
        checkTaskStatus(response)
    }, 1000)
}

// ---- Utilities ---- //
function diff(obj1,obj2) {
    var newObj = $.extend({},obj1,obj2);
    var result = {};
    $.each(newObj, function (key, value) {
        if (!obj2.hasOwnProperty(key) || !obj1.hasOwnProperty(key) || obj2[key] !== obj1[key]) {
            result[key] = [obj1[key],obj2[key]];
        }
    });

    return result;
}

function initializeAPI(){
    // Load GHI Donations API
    var ROOT = '/_ah/api';
    gapi.client.load('ghidonations', 'v1', function() {
        ghiapi = gapi.client.ghidonations

        // Trigger the event to load first page
        $(window).hashchange();
    }, ROOT);

}

$(document).ready(function(){
    if (ghiapi == null){
        show_flash("setting", "Loading GHI Donations...", false)
    }

    //Function to check if an element exists
    jQuery.fn.exists = function() {
        return jQuery(this).length > 0;
    }

    $(document).bind('cbox_open', function(){
        colorbox_open = true
    });

    $(document).bind('cbox_closed', function(){
        colorbox_open = false

        var on_review_queue = window.location.hash.indexOf("#review")
        if (loading == false & on_review_queue != -1){
            var t=setTimeout(function(){refreshTable()}, 1000)
        }

        $("#" + current_container).css("display", "none")
    });

    // Bind the event.
    bind_hashchange()

    $("#refresh").click(refreshPage)

    //Set up AJAX
    $.ajaxSetup({
        error: function(jqXHR, exception) {
            if (jqXHR.status === 0) {
                show_flash("undone", "I couldn't connect to the Internet. Check your connection.", true)
            } else if (jqXHR.status == 404) {
                show_flash("undone", "Whoops! I couldn't find that page.", true)
            } else if (jqXHR.status == 500) {
                show_flash("undone", "Uh oh! The server's not playing nice right now. Try again later.", true)
            } else if (exception === 'parsererror') {
                show_flash("undone", "Uh oh! I don't understand something on this page. Try again later.", true)
            } else if (exception === 'timeout') {
                show_flash("undone", "Hmm... the server's not responding. Try your request again.", true)
            } else {
                show_flash("undone", "Uh oh, something happened but I don't know what it is - " + jqXHR.responseText, true)
            }
        }
    });

    //This is necessary for the theme to function
    $("ul.sf-menu").superfish(); // Superfish Menus!

    $("#myaccount").click(function() {
        $("#logindrop").toggle("slow");
        return false;
    });

    $("#logindrop a").click(function(){
        $("#logindrop").toggle("slow");
    })

    $("#search").click(function() {
        $("#searchdrop").toggle("slow");
        return false;
    });

    $(".notification").click(function() {
        $(this).fadeOut("slow");
    });
});<commit_msg>Fixed variable<commit_after>var home_page = $("#home_page").val()
var previous_hash_params = {}
var previous_hash_base = ""
var ghiapi = null

// ---- Show flash message ---- //
function show_flash(type, message, fade){
    $("#flash_message").fadeOut(function(){
        $("#flash_message").html("")

        var banner = 'banner_message'
        banner = banner.replace("banner_type", type).replace("banner_message", message)
        $("#flash_message").html(banner)
        $("#flash_message").fadeIn()

        if (fade === true){
            var t = setTimeout("hide_flash()", 10000)
        }

    })
}

function hide_flash(){
    $("#flash_message").fadeOut()
}

// ---- AJAX setup ---- //
var current_colorbox = null
var current_container = null
var loading = false
var colorbox_open = false

function change_hash(e, location){
    if (e.ctrlKey){
        window.open("#" + location, "_blank")
    }
    else{
        window.location.hash = location
    }
}

function bind_hashchange(){
    $(window).hashchange(function(){
        // Fires every time the hash changes!  This thing is pretty jimmy rigged.
        var hash = window.location.hash
        var parameters = {}

        if (hash !== ""){
            var location = hash.substr(1)
            var location_split = location.split("?")

            if (location_split[0] != previous_hash_base){
                // If the base page changes, trigger change_tab
                change_tab(location)
            }
            else{
                if (location_split[1]){
                    //If there are parameters, deparam them
                    parameters = $.deparam(location_split[1])
                }

                difference = diff(previous_hash_params, parameters)
                var search_change = false
                var other_change = false
                for (var key in difference){
                    // If there is a search difference, throw flag
                    if (key == "search"){
                        search_change = true
                    }
                    // If there is any other difference in the parameter, throw flag
                    else{
                        other_change = true
                    }
                }

                // If any part of the parameters changed besides search, change tab like normal
                if (other_change == true){
                    change_tab(location)
                }
                // But if search is the only thing that changed, insert query into box and fire
                else if(search_change == true){
                    var query = parameters["search"]
                    if (query == null){
                        query = ""
                    }

                    $("#search_query").val(query)
                    trigger_search(query)
                }
            }
        }

        else{
            window.location.hash = home_page
            change_tab(home_page)
        }

        //Saving these parameters for comparison next time
        if (location_split){
            previous_hash_base = location_split[0]
        }
        else{
            previous_hash_base = ""
        }

        previous_hash_params = parameters
    })
}

function change_search_hash(query){
    var hash = window.location.hash.split("?")
    if (hash[1]){
        var parameters = $.deparam(hash[1])
        parameters["search"] = query
    }
    else{
        var parameters = {"search" : query}
    }

    hash = hash[0] + "?" + $.param(parameters)
    window.location.hash = hash
}

function change_tab(location)
{
    loading = true

    $.colorbox.close()

    show_flash("setting", "Loading page...", false)

    $("#actualbody").fadeOut(300, function(){
        //To prevent page flashing before the fadeOut is finished
        $.ajax({
            url: "/ajax/" + location,
            cache: false,
            success: function(html){
                $("#actualbody").html(html);
                hide_flash()

                //If we lost the auth cookie, bounce back to login screen instead of loading that in the body
                if ($("#login_form").exists()){
                    window.location = "/login"
                }
                else {
                    $("#actualbody").fadeIn(300)
                    $("#refresh").attr("href", window.location.hash)
                }
            }
        });
    })

    loading = false
}

function setupSearchEvents(){
    $("#search_query").focus(function(){
        $("#search_help").slideDown()
    })

    $("#search_query").focusout(function(){
        $("#search_help").slideUp()
    })

    $("#search_go").click(function(){
        var query = $("#search_query").val()
        change_search_hash(query)
    })

    $("#search_query").keyup(function(e){
        if (e.keyCode == 13){
            $("#search_go").click()
        }
    })
}

// ---- DataTables ---- //
var cursors_dict = {0:null}
var data_loading = false
var current_page = 0
var data_table = null

var pub_rpc_request = null
var pub_rpc_params = null
var pub_callback = null

function initializeTable(num_columns, rpc_request, rpc_params, callback){
    //Making these variables public
    pub_rpc_params = rpc_params
    pub_rpc_request = rpc_request
    pub_callback = callback

    // Reset cursor dict
    cursors_dict = {0:null}

    var column_array = [{bVisible:false}]

    var i=0;
    for (i=0; i<num_columns; i++)
    {
        //Makes an array that DataTables likes for formatting
        column_array.push(null)
    }

    if (data_table){
        //Destroy a table if it exists
        data_table.fnDestroy()
    }

    //Set up Datatable
    data_table = $(".dataTable").dataTable({
        "sPaginationType": "two_button",
        "bJQueryUI": true,
        "bVisible" : true,
        "bDestroy": true,
        "bAutoWidth" : true,

        "oLanguage": {
            "sEmptyTable": " "
        },

        //Disable some features
        "bLengthChange": false,
        "bFilter": false,
        "bSort": false,
        "bInfo": false,
        "iDisplayLength": 15,

        "aoColumns" : column_array
    });

    $(".dataTable").css("width", "100%")

    //Setting page to 0
    current_page = 0

    //AJAX Controls
    $(".ui-corner-left").unbind("click")
    $(".ui-corner-right").unbind("click")

    $(".ui-corner-right").click(function(){
        if (data_loading == false){
            data_loading = true
            $(this).effect("highlight", {}, 3000);

            try{
               current_page += 1
               pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
            }
            catch(e){
                current_page -= 1
                pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
            }

        }
    })

    $(".ui-corner-left").click(function(){
        if (data_loading == false){
           data_loading = true
           $(this).effect("highlight", {}, 3000);

            if (current_page >= 1){
                try{
                    current_page -= 1
                    pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
                }
                catch(e){
                    current_page += 1
                    pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
                }
            }
            else{
                show_flash("setting", "There isn't any more data to show.", true)
                pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
            }
        }
    })

    pageThrough(data_table, current_page, rpc_request, rpc_params, callback)
    return data_table
}

function pageThrough(data_table, page_number, rpc_request, rpc_params, callback){
    try{
        var query_cursor = cursors_dict[page_number]
    }
    catch(e){
        var query_cursor = "end"
    }

    if(query_cursor == "end"){
        show_flash("setting", "There isn't any more data to show.", true)
        throw "No more data"
    }

    if (query_cursor != null){
        query_cursor = query_cursor
    }

    // -- RPC to get donations -- //
    var params = {'action':rpc_request, 'query_cursor':query_cursor}

    //Adding additional parameters to params dictionary
    if (rpc_params != null){
        params = $.extend(params, rpc_params)
    }

    $("#search_icon").hide()
    $("#search_loading").show()

    var request = rpc_request(params)
    request.execute(function(response){
        $("#search_loading").hide()
        $("#search_icon").show()

        data_table.fnClearTable()

        if (response.objects){

            $.each(response.objects, function(index, d){
                callback(data_table, d)
            })
        }

        if (response.new_cursor == null){
            var new_cursor = "end"
        }
        else{
            var new_cursor = response.new_cursor
        }

        cursors_dict[page_number + 1] = new_cursor

        data_table.fnAdjustColumnSizing()

        data_loading = false
    })
}

function htmlDecode(input){
  var e = document.createElement('div');
  e.innerHTML = input;
  return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}

// ---- Colorbox ---- //
//Initialize Colorbox
function loadColorbox(url, container){
    current_colorbox = url
    current_container = container
    //console.log("Loading url: " + url + " in container: " + container)

    $.get(url, function(data) {
        $("#" + container).html(data);
        $("#" + container).css("display", "block")

        if ($("#login_page").exists()){
            window.location = "/login"
        }
        else{
            $.colorbox({inline:true, href:"#" + container, width:"80%", height:"80%"})
        }
    });
}

//Closes Colorbox
function closeColorbox(){
    $.colorbox.close()
}

function refreshPage(){
    if (colorbox_open == true){
        loadColorbox(current_colorbox, current_container)
    }
    else{
        var hash = window.location.hash
        var location = hash.substr(1)
        change_tab(location)

        cursors_dict = {0:null}
        data_loading = false
        current_page = 0
        data_table = null

        pub_rpc_request = null
        pub_rpc_params = null
        pub_callback = null
    }
}

function refreshTable(){
    pageThrough(data_table, current_page, pub_rpc_request, pub_rpc_params, pub_callback)
}

function rpcGet(data, callback){
    var url = '/rpc?' + $.param(data)
    $.getJSON(url, function(data){
        callback(data)
    })
}

function rpcPost(data, callback){
    $.ajax({type: "post",
            url: "/rpc",
            data: $.toJSON(data),
            success: function(data) {
                data = JSON.parse(data)
                if (data[0] == true){
                    show_flash("done", data[1], true)
                    callback(data)
                }
                else{

                    try{
                        message = data[1]
                        if (message == ""){
                            message = "An error occurred."
                        }
                    }
                    catch(err){
                        message = "An error occurred."
                    }

                    show_flash("undone", data[1], true)
                }
            }
        });
}

function rpcSuccessMessage(response){
    if (response.success == true){
        show_flash("done", response.message, true)
    }
    else{
        if (response.error_message){
            message = response.error_message
        }
        else{
            message = response.message
        }

        try{
            if (message == ""){
                message = "An error occurred."
            }
            else if(message == "Internal Server Error"){
                message = "Uh oh! The server's not playing nice right now. Try again later."
            }
        }
        catch(err){
            message = "An error occurred."
        }

        show_flash("undone", message, false)
        throw "Server error"
    }
}

// ---- Spreadsheet Download Controllers ---- //
function checkTaskStatus(response){
    var params2 = {"job_id":response.job_id}
    var request2 = ghiapi.spreadsheet.check(params2)

    request2.execute(function(response2){

        if (response2.completed == true){
            var url = "/ajax/spreadsheet/download?blob_key=" + response2.blob_key
            window.open(url)

            $("#download_query").html('Downloading now...')
            setTimeout(function(){
                $("#download_query").html('<button class="small button">Download query as a spreadsheet</button>')
            }, 5000)
        }
        else{
            $("#download_progress").text(response2.status)
            // If the report isn't done generating, check again later
            timeoutCheckStatus(response)
        }

    })
}

function setupDownloadQuery(mode, file_name){
    $("#download_query").delegate("button", "click", function(){
        $(this).hide()
        $("#download_query").html("Generating report - 0% finished. ")

        var query = $("#search_query").val()
        var params = {"mode":mode, "filename":file_name}
        var request = ghiapi.spreadsheet.start(params)

        request.execute(function(response){
            timeoutCheckStatus(response)
        })

    })
}

function timeoutCheckStatus(response){
    setTimeout(function(){
        checkTaskStatus(response)
    }, 1000)
}

// ---- Utilities ---- //
function diff(obj1,obj2) {
    var newObj = $.extend({},obj1,obj2);
    var result = {};
    $.each(newObj, function (key, value) {
        if (!obj2.hasOwnProperty(key) || !obj1.hasOwnProperty(key) || obj2[key] !== obj1[key]) {
            result[key] = [obj1[key],obj2[key]];
        }
    });

    return result;
}

function initializeAPI(){
    // Load GHI Donations API
    var ROOT = '/_ah/api';
    gapi.client.load('ghidonations', 'v1', function() {
        ghiapi = gapi.client.ghidonations

        // Trigger the event to load first page
        $(window).hashchange();
    }, ROOT);

}

$(document).ready(function(){
    if (ghiapi == null){
        show_flash("setting", "Loading GHI Donations...", false)
    }

    //Function to check if an element exists
    jQuery.fn.exists = function() {
        return jQuery(this).length > 0;
    }

    $(document).bind('cbox_open', function(){
        colorbox_open = true
    });

    $(document).bind('cbox_closed', function(){
        colorbox_open = false

        var on_review_queue = window.location.hash.indexOf("#review")
        if (loading == false & on_review_queue != -1){
            var t=setTimeout(function(){refreshTable()}, 1000)
        }

        $("#" + current_container).css("display", "none")
    });

    // Bind the event.
    bind_hashchange()

    $("#refresh").click(refreshPage)

    //Set up AJAX
    $.ajaxSetup({
        error: function(jqXHR, exception) {
            if (jqXHR.status === 0) {
                show_flash("undone", "I couldn't connect to the Internet. Check your connection.", true)
            } else if (jqXHR.status == 404) {
                show_flash("undone", "Whoops! I couldn't find that page.", true)
            } else if (jqXHR.status == 500) {
                show_flash("undone", "Uh oh! The server's not playing nice right now. Try again later.", true)
            } else if (exception === 'parsererror') {
                show_flash("undone", "Uh oh! I don't understand something on this page. Try again later.", true)
            } else if (exception === 'timeout') {
                show_flash("undone", "Hmm... the server's not responding. Try your request again.", true)
            } else {
                show_flash("undone", "Uh oh, something happened but I don't know what it is - " + jqXHR.responseText, true)
            }
        }
    });

    //This is necessary for the theme to function
    $("ul.sf-menu").superfish(); // Superfish Menus!

    $("#myaccount").click(function() {
        $("#logindrop").toggle("slow");
        return false;
    });

    $("#logindrop a").click(function(){
        $("#logindrop").toggle("slow");
    })

    $("#search").click(function() {
        $("#searchdrop").toggle("slow");
        return false;
    });

    $(".notification").click(function() {
        $(this).fadeOut("slow");
    });
});<|endoftext|>