<commit_before>'use strict';

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

const Exchange = require ('./base/Exchange');
const { DECIMAL_PLACES } = require ('./base/functions/number');

module.exports = class OceanEx extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'oceanex',
            'name': 'OceanEx',
            'countries': [ 'SG' ],
            'urls': {
                'api': 'https://api.oceanex.pro/v1/',
                'www': 'https://www.oceanex.pro.com',
                'doc': 'https://api.oceanex.pro/doc/v1/',
            },
            'api': {
                'public': {
                    'get': [
                        'markets',
                        'tickers',
                        'tickers_multi',
                        'order_book',
                        'order_book/multi',
                        'fees/trading',
                        'trades',
                        'timestamp',
                    ],
                },
                'private': {
                    'get': [
                        'key',
                        'members/me',
                        'orders',
                        'orders_filter',
                    ],
                    'post': [
                        'orders',
                        'orders/multi',
                        'order/delete',
                        'order/delete/multi',
                        'orders/clear',
                    ],
                },
            },
            'version': 'v1',
            'rateLimit': 1000,
            'has': {
                'fetchMarkets': true,
                'fetchCurrencies': false,
                'fetchTicker': true,
                'fetchOrderBook': true,
                'fetchTrades': true,
                'fetchTradingLimits': false,
                'fetchTradingFees': true,
                'fetchFundingFees': false,
                'fetchOrder': true,
                'fetchBalance': true,
                'createOrder': true,
                'cancelOrder': true,
                'cancelAllOrders': true,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '12h': '12h',
                '1d': '1d',
                '1w': '1w',
            },
        });
    }

    async fetchMarkets () {
        let response = await this.publicGetMarkets ();
        const result = [];
        const markets = this.safeValue (response, 'data');
        for (let i = 0; i < markets.length; i++) {
            const market = markets[i];
            let id = market['id'].toUpperCase ();
            let symbol = market['name'];
            let pair = market['name'].split ('/');
            let base = pair[0];
            let quote = pair[1];
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': base,
                'quoteId': quote,
                'active': true,
                'info': market,
                'precision': undefined,
                'limits': undefined,
            });
        }
        // parse markets
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        let response = await this.publicGetTickers ({ 'market': symbol });
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseTicker (data, symbol);
    }

    async fetchTickers(symbol = undefined, params = {}) {
        let response = await this.publicGetTickersMulti (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseTicker (data, );
    }

    parseTicker (data, market = undefined) {
        let timestamp = data['at'];
        const ticker = data['ticker'];
        if (typeof timestamp === 'string') {
            if (timestamp.length > 0) {
                timestamp = this.parse8601 (timestamp);
            }
        }
        return {
            'symbol': market,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'buy'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'sell'),
            'vwap': undefined,
            'open': undefined,
            'close': this.safeFloat (ticker, 'last'),
            'last': this.safeFloat (ticker, 'last'),
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'volume'),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        let response = await this.publicGetOrderBook (this.extend ({
            'market': symbol,
            'limit': limit,
        }, params));
        const orderbook = this.safeValue (response, 'data');
        return this.parseOrderBook (orderbook, orderbook['timestamp']);
    }

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

    parseTrade (trade, market = undefined) {
        let side = undefined;
        if (trade['side'] === 'bid') {
            side = 'buy';
        } else if (trade['side'] === 'ask') {
            side = 'sell';
        }
        return {
            'info': trade,
            'timestamp': trade['created_on'],
            'datetime': trade['created_at'],
            'symbol': trade['market'],
            'id': trade['id'],
            'order': undefined,
            'type': 'limit',
            'takerOrMaker': undefined,
            'side': side,
            'price': this.safeFloat (trade, 'price'),
            'amount': this.safeFloat (trade, 'volume'),
            'cost': undefined,
            'fee': undefined,
        };
    }

    async fetchTime () {
        let response = await this.publicGetTimestamp ();
        return response['data'];
    }

    async fetchTradingFees () {
        let response = await this.publicGetFeesTrading ();
        return response['data'];
    }

    // private method input value {'user_jwt': encrypted_data}
    async fetchKey (params = {}) {
        let response = await this.privateGetKey (this.extend (params));
        return response['data'];
    }

    async fetchBalance (params = {}) {
        let response = await this.privateGetMembersMe (this.extend (params));
        const balances = this.safeValue (response['data'], 'accounts');
        let result = { 'info': balances };
        for (let i = 0; i < balances.length; i++) {
            const balance = balances[i];
            let currency = balance['currency'];
            let account = this.account ();
            let free = this.safeFloat (balance, 'balance');
            let used = this.safeFloat (balance, 'locked');
            let total = free + used;
            account['free'] = free;
            account['used'] = used;
            account['total'] = total;
            result[currency] = account;
        }
        return this.parseBalance(result);
    }

    async createOrder (params = {}) {
        let response = await this.privatePostOrders (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        let result = {
            'info': data,
            'id': data['id'],
            'symbol': data['market'],
            'type': data['ord_type'],
            'side': data['side'],
            'status': data['state'],
        };
        return result;
    }

    async fetchOrders (params = {}) {
        let response = await this.privateGetOrders (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseOrders (data);
    }

    parseOrder (order, market = undefined) {
        let result = {
            'info': order,
            'id': order['id'],
            'timestamp': order['created_on'],
            'datetime': order['created_at'],
            'lastTradeTimestamp': undefined,
            'symbol': order['market'],
            'type': order['ord_type'],
            'side': order['side'],
            'price': this.safeFloat (order, 'price'),
            'average': this.safeFloat (order, 'avg_price'),
            'amount': this.safeFloat (order, 'volume'),
            'remaining': this.safeFloat (order, 'remaining_volume'),
            'filled': this.safeFloat (order, 'executed_volume'),
            'status': order['state'],
            'fee': undefined,
        };
        return result;
    }

    async createMultiOrders (params = {}) {
        let response = await this.privatePostOrdersMulti (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseOrders (data);
    }

    async filterOrders (params = {}) {
        let response = await this.privateGetOrdersFilter (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseOrders (data);
    }

    async cancelOrder (params = {}) {
        let response = await this.privatePostOrderDelete (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseOrder (data);
    }

    async cancelMultiOrders (params = {}) {
        let response = await this.privatePostOrderDeleteMulti (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseOrders (data);
    }

    async clearOrder (params = {}) {
        let response = await this.privatePostOrdersClear (this.extend (params));
        this.isValidResponse (response);
        const data = response['data'];
        return this.parseOrders (data);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'];
        headers = { 'Content-Type': 'application/json-rpc' };
        if (Object.keys (params).length) {
            if (path === 'tickers') {
                url += path + '/' + params['market'];
            } else {
                url += path + '?' + this.urlencode (params);
            }
        } else {
            url += path;
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    isValidResponse (response) {
        if (response['code'] !== 0) {
            throw invalidResponseError (response['message']);
        }
        return true;
    }
};<commit_msg>Update oceanex.js and fix building error<commit_after>'use strict';

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

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

module.exports = class oceanex extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'oceanex',
            'name': 'oceanex',
            'countries': [ 'SG' ],
            'urls': {
                'api': 'https://api.oceanex.pro/v1/',
                'www': 'https://www.oceanex.pro.com',
                'doc': 'https://api.oceanex.pro/doc/v1/',
            },
            'api': {
                'public': {
                    'get': [
                        'markets',
                        'tickers/{pair}/',
                        'tickers_multi',
                        'order_book',
                        'order_book/multi',
                        'fees/trading',
                        'trades',
                        'timestamp',
                    ],
                },
                'private': {
                    'get': [
                        'key',
                        'members/me',
                        'orders',
                        'orders_filter',
                    ],
                    'post': [
                        'orders',
                        'orders/multi',
                        'order/delete',
                        'order/delete/multi',
                        'orders/clear',
                    ],
                },
            },
            'version': 'v1',
            'rateLimit': 1000,
            'has': {
                'fetchMarkets': true,
                'fetchCurrencies': false,
                'fetchTicker': true,
                'fetchOrderBook': true,
                'fetchTrades': true,
                'fetchTradingLimits': false,
                'fetchTradingFees': true,
                'fetchFundingFees': false,
                'fetchTime': true,
                'fetchOrders': true,
                'fetchBalance': true,
                'createOrder': true,
                'cancelOrder': true,
                'cancelAllOrders': true,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '12h': '12h',
                '1d': '1d',
                '1w': '1w',
            },
        });
    }

    async fetchMarkets (params = {}) {
        let response = await this.publicGetMarkets ();
        const result = [];
        const markets = this.safeValue (response, 'data');
        for (let i = 0; i < markets.length; i++) {
            const market = markets[i];
            let id = market['id'].toUpperCase ();
            let symbol = market['name'];
            let pair = market['name'].split ('/');
            let base = pair[0];
            let quote = pair[1];
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': base,
                'quoteId': quote,
                'active': true,
                'info': market,
                'precision': undefined,
                'limits': undefined,
            });
        }
        // parse markets
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        let response = await this.publicGetTickersPair (this.extend ({ 'pair': symbol }), params);
        const data = response['data'];
        return this.parseTicker (data, symbol);
    }

    parseTicker (data, market = undefined) {
        let timestamp = data['at'];
        const ticker = data['ticker'];
        if (typeof timestamp === 'string') {
            if (timestamp.length > 0) {
                timestamp = this.parse8601 (timestamp);
            }
        }
        return {
            'symbol': market,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'buy'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'sell'),
            'vwap': undefined,
            'open': undefined,
            'close': this.safeFloat (ticker, 'last'),
            'last': this.safeFloat (ticker, 'last'),
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'volume'),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        let response = await this.publicGetOrderBook (this.extend ({
            'market': symbol,
            'limit': limit,
        }, params));
        const orderbook = this.safeValue (response, 'data');
        return this.parseOrderBook (orderbook, orderbook['timestamp']);
    }

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

    parseTrade (trade, market = undefined) {
        let side = undefined;
        if (trade['side'] === 'bid') {
            side = 'buy';
        } else if (trade['side'] === 'ask') {
            side = 'sell';
        }
        return {
            'info': trade,
            'timestamp': trade['created_on'],
            'datetime': trade['created_at'],
            'symbol': trade['market'],
            'id': trade['id'],
            'order': undefined,
            'type': 'limit',
            'takerOrMaker': undefined,
            'side': side,
            'price': this.safeFloat (trade, 'price'),
            'amount': this.safeFloat (trade, 'volume'),
            'cost': undefined,
            'fee': undefined,
        };
    }

    async fetchTime (params = {}) {
        let response = await this.publicGetTimestamp ();
        return response['data'];
    }

    async fetchTradingFees (params = {}) {
        let response = await this.publicGetFeesTrading ();
        return response['data'];
    }

    async fetchKey (params = {}) {
        // private method input value {'user_jwt': encrypted_jwt_token}
        // jwt_token should be RS256 encoded
        let response = await this.privateGetKey (this.extend (params));
        return response['data'];
    }

    async fetchBalance (params = {}) {
        let response = await this.privateGetMembersMe (this.extend (params));
        const balances = this.safeValue (response['data'], 'accounts');
        let result = { 'info': balances };
        for (let i = 0; i < balances.length; i++) {
            const balance = balances[i];
            let currency = balance['currency'];
            let account = this.account ();
            let free = this.safeFloat (balance, 'balance');
            let used = this.safeFloat (balance, 'locked');
            let total = free + used;
            account['free'] = free;
            account['used'] = used;
            account['total'] = total;
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        let response = await this.privatePostOrders (this.extend (params));
        const data = response['data'];
        let result = {
            'info': data,
            'id': data['id'],
            'symbol': data['market'],
            'type': data['ord_type'],
            'side': data['side'],
            'status': data['state'],
        };
        return result;
    }

    async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        let response = await this.privateGetOrders (this.extend (params));
        const data = response['data'];
        return this.parseOrders (data);
    }

    parseOrder (order, market = undefined) {
        let result = {
            'info': order,
            'id': order['id'],
            'timestamp': order['created_on'],
            'datetime': order['created_at'],
            'lastTradeTimestamp': undefined,
            'symbol': order['market'],
            'type': order['ord_type'],
            'side': order['side'],
            'price': this.safeFloat (order, 'price'),
            'average': this.safeFloat (order, 'avg_price'),
            'amount': this.safeFloat (order, 'volume'),
            'remaining': this.safeFloat (order, 'remaining_volume'),
            'filled': this.safeFloat (order, 'executed_volume'),
            'status': order['state'],
            'fee': undefined,
        };
        return result;
    }

    async createMultiOrders (params = {}) {
        let response = await this.privatePostOrdersMulti (this.extend (params));
        const data = response['data'];
        return this.parseOrders (data);
    }

    async filterOrders (params = {}) {
        let response = await this.privateGetOrdersFilter (this.extend (params));
        const data = response['data'];
        return this.parseOrders (data);
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        let response = await this.privatePostOrderDelete (this.extend (params));
        const data = response['data'];
        return this.parseOrder (data);
    }

    async cancelMulitOrders (params = {}) {
        let response = await this.privatePostOrderDeleteMulti (this.extend (params));
        const data = response['data'];
        return this.parseOrders (data);
    }

    async cancelAllOrders (symbol = undefined, params = {}) {
        let response = await this.privatePostOrdersClear (this.extend (params));
        const data = response['data'];
        return this.parseOrders (data);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (Object.keys (query).length) {
            url += '?' + this.urlencode (query);
        }
        headers = { 'Content-Type': 'application/json' };
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (code, reason, url, method, headers, body, response) {
        // API response structure
        // HTTP code : 200 (success to send request to API) / 201 (success to create order)
        // {"code": , (success return 0. other code indicate error)
        //  "message": "", (if error, contain error message report)
        //  "data": []}
        if (response === undefined) {
            return;
        }
        if (code === 200 || code === 201) {
            const resCode = this.safeValue (response, 'code');
            if (resCode === undefined) {
                throw new ExchangeError (response);
            }
            if (resCode !== 0) {
                throw new ExchangeError (response['message']);
            }
        }
    }
};<|endoftext|>


<commit_before>const StringUtil = require('../util/string-util');
const log = require('../util/log');
const {loadSvgString, serializeSvgToString} = require('scratch-svg-renderer');

const loadVector_ = function (costume, runtime, rotationCenter, optVersion) {
    return new Promise(resolve => {
        const svgString = costume.asset.decodeText();
        // scratch-svg-renderer fixes syntax that causes loading issues,
        // and if optVersion is 2, fixes "quirks" associated with Scratch 2 SVGs,
        const fixedSvgString = serializeSvgToString(loadSvgString(svgString, optVersion === 2/* fromVersion2 */));

        // If the string changed, put back into storage
        if (svgString !== fixedSvgString) {
            const storage = runtime.storage;
            costume.asset.encodeTextData(fixedSvgString, storage.DataFormat.SVG, true);
            costume.assetId = costume.asset.assetId;
            costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
        }

        // createSVGSkin does the right thing if rotationCenter isn't provided, so it's okay if it's
        // undefined here
        costume.skinId = runtime.renderer.createSVGSkin(fixedSvgString, rotationCenter);
        costume.size = runtime.renderer.getSkinSize(costume.skinId);
        // Now we should have a rotationCenter even if we didn't before
        if (!rotationCenter) {
            rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
            costume.rotationCenterX = rotationCenter[0];
            costume.rotationCenterY = rotationCenter[1];
            costume.bitmapResolution = 1;
        }

        resolve(costume);
    });
};

const canvasPool = (function () {
    /**
     * A pool of canvas objects that can be reused to reduce memory
     * allocations. And time spent in those allocations and the later garbage
     * collection.
     */
    class CanvasPool {
        constructor () {
            this.pool = [];
            this.clearSoon = null;
        }

        /**
         * After a short wait period clear the pool to let the VM collect
         * garbage.
         */
        clear () {
            if (!this.clearSoon) {
                this.clearSoon = new Promise(resolve => setTimeout(resolve, 1000))
                    .then(() => {
                        this.pool.length = 0;
                        this.clearSoon = null;
                    });
            }
        }

        /**
         * Return a canvas. Create the canvas if the pool is empty.
         * @returns {HTMLCanvasElement} A canvas element.
         */
        create () {
            return this.pool.pop() || document.createElement('canvas');
        }

        /**
         * Release the canvas to be reused.
         * @param {HTMLCanvasElement} canvas A canvas element.
         */
        release (canvas) {
            this.clear();
            this.pool.push(canvas);
        }
    }

    return new CanvasPool();
}());

/**
 * Return a promise to fetch a bitmap from storage and return it as a canvas
 * If the costume has bitmapResolution 1, it will be converted to bitmapResolution 2 here (the standard for Scratch 3)
 * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function
 * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information.
 * @param {!object} costume - the Scratch costume object.
 * @param {!Runtime} runtime - Scratch runtime, used to access the v2BitmapAdapter
 * @param {?object} rotationCenter - optionally passed in coordinates for the center of rotation for the image. If
 *     none is given, the rotation center of the costume will be set to the middle of the costume later on.
 * @property {number} costume.bitmapResolution - the resolution scale for a bitmap costume.
 * @returns {?Promise} - a promise which will resolve to an object {canvas, rotationCenter, assetMatchesBase},
 *     or reject on error.
 *     assetMatchesBase is true if the asset matches the base layer; false if it required adjustment
 */
const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) {
    if (!costume || !costume.asset) {
        return Promise.reject('Costume load failed. Assets were missing.');
    }
    if (!runtime.v2BitmapAdapter) {
        return Promise.reject('No V2 Bitmap adapter present.');
    }

    return Promise.all([costume.asset, costume.textLayerAsset].map(asset => {
        if (!asset) {
            return null;
        }

        if (typeof createImageBitmap !== 'undefined') {
            return createImageBitmap(
                new Blob([asset.data], {type: asset.assetType.contentType})
            );
        }

        return new Promise((resolve, reject) => {
            const image = new Image();
            image.onload = function () {
                resolve(image);
                image.onload = null;
                image.onerror = null;
            };
            image.onerror = function () {
                reject('Costume load failed. Asset could not be read.');
                image.onload = null;
                image.onerror = null;
            };
            image.src = asset.encodeDataURI();
        });
    }))
        .then(([baseImageElement, textImageElement]) => {
            const mergeCanvas = canvasPool.create();

            const scale = costume.bitmapResolution === 1 ? 2 : 1;
            mergeCanvas.width = baseImageElement.width;
            mergeCanvas.height = baseImageElement.height;

            const ctx = mergeCanvas.getContext('2d');
            ctx.drawImage(baseImageElement, 0, 0);
            if (textImageElement) {
                ctx.drawImage(textImageElement, 0, 0);
            }
            // Track the canvas we merged the bitmaps onto separately from the
            // canvas that we receive from resize if scale is not 1. We know
            // resize treats mergeCanvas as read only data. We don't know when
            // resize may use or modify the canvas. So we'll only release the
            // mergeCanvas back into the canvas pool. Reusing the canvas from
            // resize may cause errors.
            let canvas = mergeCanvas;
            if (scale !== 1) {
                canvas = runtime.v2BitmapAdapter.resize(mergeCanvas, canvas.width * scale, canvas.height * scale);
            }

            // By scaling, we've converted it to bitmap resolution 2
            if (rotationCenter) {
                rotationCenter[0] = rotationCenter[0] * scale;
                rotationCenter[1] = rotationCenter[1] * scale;
                costume.rotationCenterX = rotationCenter[0];
                costume.rotationCenterY = rotationCenter[1];
            }
            costume.bitmapResolution = 2;

            // Clean up the costume object
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;

            return {
                canvas,
                mergeCanvas,
                rotationCenter,
                // True if the asset matches the base layer; false if it required adjustment
                assetMatchesBase: scale === 1 && !textImageElement
            };
        })
        .catch(() => {
            // Clean up the text layer properties if it fails to load
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;
        });
};

const loadBitmap_ = function (costume, runtime, _rotationCenter) {
    return fetchBitmapCanvas_(costume, runtime, _rotationCenter)
        .then(fetched => {
            const updateCostumeAsset = function (dataURI) {
                if (!runtime.v2BitmapAdapter) {
                    // TODO: This might be a bad practice since the returned
                    // promise isn't acted on. If this is something we should be
                    // creating a rejected promise for we should also catch it
                    // somewhere and act on that error (like logging).
                    //
                    // Return a rejection to stop executing updateCostumeAsset.
                    return Promise.reject('No V2 Bitmap adapter present.');
                }

                const storage = runtime.storage;
                costume.asset = storage.createAsset(
                    storage.AssetType.ImageBitmap,
                    storage.DataFormat.PNG,
                    runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI),
                    null,
                    true // generate md5
                );
                costume.dataFormat = storage.DataFormat.PNG;
                costume.assetId = costume.asset.assetId;
                costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
            };

            if (!fetched.assetMatchesBase) {
                updateCostumeAsset(fetched.canvas.toDataURL());
            }

            return fetched;
        })
        .then(({canvas, mergeCanvas, rotationCenter}) => {
            // createBitmapSkin does the right thing if costume.rotationCenter is undefined.
            // That will be the case if you upload a bitmap asset or create one by taking a photo.
            let center;
            if (rotationCenter) {
                // fetchBitmapCanvas will ensure that the costume's bitmap resolution is 2 and its rotation center is
                // scaled to match, so it's okay to always divide by 2.
                center = [
                    rotationCenter[0] / 2,
                    rotationCenter[1] / 2
                ];
            }

            // TODO: costume.bitmapResolution will always be 2 at this point because of fetchBitmapCanvas_, so we don't
            // need to pass it in here.
            costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, center);
            canvasPool.release(mergeCanvas);
            const renderSize = runtime.renderer.getSkinSize(costume.skinId);
            costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2

            if (!rotationCenter) {
                rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
                // Actual rotation center, since all bitmaps are resolution 2
                costume.rotationCenterX = rotationCenter[0] * 2;
                costume.rotationCenterY = rotationCenter[1] * 2;
                costume.bitmapResolution = 2;
            }
            return costume;
        });
};

/**
 * Initialize a costume from an asset asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @property {!Asset} costume.asset - the asset of the costume loaded from storage.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostumeFromAsset = function (costume, runtime, optVersion) {
    costume.assetId = costume.asset.assetId;
    const renderer = runtime.renderer;
    if (!renderer) {
        log.error('No rendering module present; cannot load costume: ', costume.name);
        return Promise.resolve(costume);
    }
    const AssetType = runtime.storage.AssetType;
    let rotationCenter;
    // Use provided rotation center and resolution if they are defined. Bitmap resolution
    // should only ever be 1 or 2.
    if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) &&
            typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) {
        rotationCenter = [costume.rotationCenterX, costume.rotationCenterY];
    }
    if (costume.asset.assetType.runtimeFormat === AssetType.ImageVector.runtimeFormat) {
        return loadVector_(costume, runtime, rotationCenter, optVersion)
            .catch(error => {
                log.warn(`Error loading vector image: ${error.name}: ${error.message}`);
                // Use default asset if original fails to load
                costume.assetId = runtime.storage.defaultAssetId.ImageVector;
                costume.asset = runtime.storage.get(costume.assetId);
                costume.md5 = `${costume.assetId}.${AssetType.ImageVector.runtimeFormat}`;
                return loadVector_(costume, runtime);
            });
    }
    return loadBitmap_(costume, runtime, rotationCenter, optVersion);
};

/**
 * Load a costume's asset into memory asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!string} md5ext - the MD5 and extension of the costume to be loaded.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostume = function (md5ext, costume, runtime, optVersion) {
    const idParts = StringUtil.splitFirst(md5ext, '.');
    const md5 = idParts[0];
    const ext = idParts[1].toLowerCase();
    costume.dataFormat = ext;

    if (costume.asset) {
        // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or file
        return loadCostumeFromAsset(costume, runtime, optVersion);
    }

    // Need to load the costume from storage. The server should have a reference to this md5.
    if (!runtime.storage) {
        log.error('No storage module present; cannot load costume asset: ', md5ext);
        return Promise.resolve(costume);
    }

    if (!runtime.storage.defaultAssetId) {
        log.error(`No default assets found`);
        return Promise.resolve(costume);
    }

    const AssetType = runtime.storage.AssetType;
    const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap;

    const costumePromise = runtime.storage.load(assetType, md5, ext);
    if (!costumePromise) {
        log.error(`Couldn't fetch costume asset: ${md5ext}`);
        return;
    }

    let textLayerPromise;
    if (costume.textLayerMD5) {
        textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png');
    } else {
        textLayerPromise = Promise.resolve(null);
    }

    return Promise.all([costumePromise, textLayerPromise]).then(assetArray => {
        costume.asset = assetArray[0];
        if (assetArray[1]) {
            costume.textLayerAsset = assetArray[1];
        }
        return loadCostumeFromAsset(costume, runtime, optVersion);
    });
};

module.exports = {
    loadCostume,
    loadCostumeFromAsset
};
<commit_msg>Go back to only using svgrenderer to process vectors that we think are coming from scratch 2<commit_after>const StringUtil = require('../util/string-util');
const log = require('../util/log');
const {loadSvgString, serializeSvgToString} = require('scratch-svg-renderer');

const loadVector_ = function (costume, runtime, rotationCenter, optVersion) {
    return new Promise(resolve => {
        let svgString = costume.asset.decodeText();
        // SVG Renderer load fixes "quirks" associated with Scratch 2 projects
        if (optVersion && optVersion === 2) {
            // scratch-svg-renderer fixes syntax that causes loading issues,
            // and if optVersion is 2, fixes "quirks" associated with Scratch 2 SVGs,
            const fixedSvgString = serializeSvgToString(loadSvgString(svgString, true /* fromVersion2 */));

            // If the string changed, put back into storage
            if (svgString !== fixedSvgString) {
                svgString = fixedSvgString;
                const storage = runtime.storage;
                costume.asset.encodeTextData(fixedSvgString, storage.DataFormat.SVG, true);
                costume.assetId = costume.asset.assetId;
                costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
            }
        }

        // createSVGSkin does the right thing if rotationCenter isn't provided, so it's okay if it's
        // undefined here
        costume.skinId = runtime.renderer.createSVGSkin(svgString, rotationCenter);
        costume.size = runtime.renderer.getSkinSize(costume.skinId);
        // Now we should have a rotationCenter even if we didn't before
        if (!rotationCenter) {
            rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
            costume.rotationCenterX = rotationCenter[0];
            costume.rotationCenterY = rotationCenter[1];
            costume.bitmapResolution = 1;
        }

        resolve(costume);
    });
};

const canvasPool = (function () {
    /**
     * A pool of canvas objects that can be reused to reduce memory
     * allocations. And time spent in those allocations and the later garbage
     * collection.
     */
    class CanvasPool {
        constructor () {
            this.pool = [];
            this.clearSoon = null;
        }

        /**
         * After a short wait period clear the pool to let the VM collect
         * garbage.
         */
        clear () {
            if (!this.clearSoon) {
                this.clearSoon = new Promise(resolve => setTimeout(resolve, 1000))
                    .then(() => {
                        this.pool.length = 0;
                        this.clearSoon = null;
                    });
            }
        }

        /**
         * Return a canvas. Create the canvas if the pool is empty.
         * @returns {HTMLCanvasElement} A canvas element.
         */
        create () {
            return this.pool.pop() || document.createElement('canvas');
        }

        /**
         * Release the canvas to be reused.
         * @param {HTMLCanvasElement} canvas A canvas element.
         */
        release (canvas) {
            this.clear();
            this.pool.push(canvas);
        }
    }

    return new CanvasPool();
}());

/**
 * Return a promise to fetch a bitmap from storage and return it as a canvas
 * If the costume has bitmapResolution 1, it will be converted to bitmapResolution 2 here (the standard for Scratch 3)
 * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function
 * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information.
 * @param {!object} costume - the Scratch costume object.
 * @param {!Runtime} runtime - Scratch runtime, used to access the v2BitmapAdapter
 * @param {?object} rotationCenter - optionally passed in coordinates for the center of rotation for the image. If
 *     none is given, the rotation center of the costume will be set to the middle of the costume later on.
 * @property {number} costume.bitmapResolution - the resolution scale for a bitmap costume.
 * @returns {?Promise} - a promise which will resolve to an object {canvas, rotationCenter, assetMatchesBase},
 *     or reject on error.
 *     assetMatchesBase is true if the asset matches the base layer; false if it required adjustment
 */
const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) {
    if (!costume || !costume.asset) {
        return Promise.reject('Costume load failed. Assets were missing.');
    }
    if (!runtime.v2BitmapAdapter) {
        return Promise.reject('No V2 Bitmap adapter present.');
    }

    return Promise.all([costume.asset, costume.textLayerAsset].map(asset => {
        if (!asset) {
            return null;
        }

        if (typeof createImageBitmap !== 'undefined') {
            return createImageBitmap(
                new Blob([asset.data], {type: asset.assetType.contentType})
            );
        }

        return new Promise((resolve, reject) => {
            const image = new Image();
            image.onload = function () {
                resolve(image);
                image.onload = null;
                image.onerror = null;
            };
            image.onerror = function () {
                reject('Costume load failed. Asset could not be read.');
                image.onload = null;
                image.onerror = null;
            };
            image.src = asset.encodeDataURI();
        });
    }))
        .then(([baseImageElement, textImageElement]) => {
            const mergeCanvas = canvasPool.create();

            const scale = costume.bitmapResolution === 1 ? 2 : 1;
            mergeCanvas.width = baseImageElement.width;
            mergeCanvas.height = baseImageElement.height;

            const ctx = mergeCanvas.getContext('2d');
            ctx.drawImage(baseImageElement, 0, 0);
            if (textImageElement) {
                ctx.drawImage(textImageElement, 0, 0);
            }
            // Track the canvas we merged the bitmaps onto separately from the
            // canvas that we receive from resize if scale is not 1. We know
            // resize treats mergeCanvas as read only data. We don't know when
            // resize may use or modify the canvas. So we'll only release the
            // mergeCanvas back into the canvas pool. Reusing the canvas from
            // resize may cause errors.
            let canvas = mergeCanvas;
            if (scale !== 1) {
                canvas = runtime.v2BitmapAdapter.resize(mergeCanvas, canvas.width * scale, canvas.height * scale);
            }

            // By scaling, we've converted it to bitmap resolution 2
            if (rotationCenter) {
                rotationCenter[0] = rotationCenter[0] * scale;
                rotationCenter[1] = rotationCenter[1] * scale;
                costume.rotationCenterX = rotationCenter[0];
                costume.rotationCenterY = rotationCenter[1];
            }
            costume.bitmapResolution = 2;

            // Clean up the costume object
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;

            return {
                canvas,
                mergeCanvas,
                rotationCenter,
                // True if the asset matches the base layer; false if it required adjustment
                assetMatchesBase: scale === 1 && !textImageElement
            };
        })
        .catch(() => {
            // Clean up the text layer properties if it fails to load
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;
        });
};

const loadBitmap_ = function (costume, runtime, _rotationCenter) {
    return fetchBitmapCanvas_(costume, runtime, _rotationCenter)
        .then(fetched => {
            const updateCostumeAsset = function (dataURI) {
                if (!runtime.v2BitmapAdapter) {
                    // TODO: This might be a bad practice since the returned
                    // promise isn't acted on. If this is something we should be
                    // creating a rejected promise for we should also catch it
                    // somewhere and act on that error (like logging).
                    //
                    // Return a rejection to stop executing updateCostumeAsset.
                    return Promise.reject('No V2 Bitmap adapter present.');
                }

                const storage = runtime.storage;
                costume.asset = storage.createAsset(
                    storage.AssetType.ImageBitmap,
                    storage.DataFormat.PNG,
                    runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI),
                    null,
                    true // generate md5
                );
                costume.dataFormat = storage.DataFormat.PNG;
                costume.assetId = costume.asset.assetId;
                costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
            };

            if (!fetched.assetMatchesBase) {
                updateCostumeAsset(fetched.canvas.toDataURL());
            }

            return fetched;
        })
        .then(({canvas, mergeCanvas, rotationCenter}) => {
            // createBitmapSkin does the right thing if costume.rotationCenter is undefined.
            // That will be the case if you upload a bitmap asset or create one by taking a photo.
            let center;
            if (rotationCenter) {
                // fetchBitmapCanvas will ensure that the costume's bitmap resolution is 2 and its rotation center is
                // scaled to match, so it's okay to always divide by 2.
                center = [
                    rotationCenter[0] / 2,
                    rotationCenter[1] / 2
                ];
            }

            // TODO: costume.bitmapResolution will always be 2 at this point because of fetchBitmapCanvas_, so we don't
            // need to pass it in here.
            costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, center);
            canvasPool.release(mergeCanvas);
            const renderSize = runtime.renderer.getSkinSize(costume.skinId);
            costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2

            if (!rotationCenter) {
                rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
                // Actual rotation center, since all bitmaps are resolution 2
                costume.rotationCenterX = rotationCenter[0] * 2;
                costume.rotationCenterY = rotationCenter[1] * 2;
                costume.bitmapResolution = 2;
            }
            return costume;
        });
};

/**
 * Initialize a costume from an asset asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @property {!Asset} costume.asset - the asset of the costume loaded from storage.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostumeFromAsset = function (costume, runtime, optVersion) {
    costume.assetId = costume.asset.assetId;
    const renderer = runtime.renderer;
    if (!renderer) {
        log.error('No rendering module present; cannot load costume: ', costume.name);
        return Promise.resolve(costume);
    }
    const AssetType = runtime.storage.AssetType;
    let rotationCenter;
    // Use provided rotation center and resolution if they are defined. Bitmap resolution
    // should only ever be 1 or 2.
    if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) &&
            typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) {
        rotationCenter = [costume.rotationCenterX, costume.rotationCenterY];
    }
    if (costume.asset.assetType.runtimeFormat === AssetType.ImageVector.runtimeFormat) {
        return loadVector_(costume, runtime, rotationCenter, optVersion)
            .catch(error => {
                log.warn(`Error loading vector image: ${error.name}: ${error.message}`);
                // Use default asset if original fails to load
                costume.assetId = runtime.storage.defaultAssetId.ImageVector;
                costume.asset = runtime.storage.get(costume.assetId);
                costume.md5 = `${costume.assetId}.${AssetType.ImageVector.runtimeFormat}`;
                return loadVector_(costume, runtime);
            });
    }
    return loadBitmap_(costume, runtime, rotationCenter, optVersion);
};

/**
 * Load a costume's asset into memory asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!string} md5ext - the MD5 and extension of the costume to be loaded.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostume = function (md5ext, costume, runtime, optVersion) {
    const idParts = StringUtil.splitFirst(md5ext, '.');
    const md5 = idParts[0];
    const ext = idParts[1].toLowerCase();
    costume.dataFormat = ext;

    if (costume.asset) {
        // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or file
        return loadCostumeFromAsset(costume, runtime, optVersion);
    }

    // Need to load the costume from storage. The server should have a reference to this md5.
    if (!runtime.storage) {
        log.error('No storage module present; cannot load costume asset: ', md5ext);
        return Promise.resolve(costume);
    }

    if (!runtime.storage.defaultAssetId) {
        log.error(`No default assets found`);
        return Promise.resolve(costume);
    }

    const AssetType = runtime.storage.AssetType;
    const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap;

    const costumePromise = runtime.storage.load(assetType, md5, ext);
    if (!costumePromise) {
        log.error(`Couldn't fetch costume asset: ${md5ext}`);
        return;
    }

    let textLayerPromise;
    if (costume.textLayerMD5) {
        textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png');
    } else {
        textLayerPromise = Promise.resolve(null);
    }

    return Promise.all([costumePromise, textLayerPromise]).then(assetArray => {
        costume.asset = assetArray[0];
        if (assetArray[1]) {
            costume.textLayerAsset = assetArray[1];
        }
        return loadCostumeFromAsset(costume, runtime, optVersion);
    });
};

module.exports = {
    loadCostume,
    loadCostumeFromAsset
};
<|endoftext|>


<commit_before>/* https://github.com/vufind-org/autocomplete.js (v2.1.2) */
function Autocomplete(_settings) {
  const _DEFAULTS = {
    delay: 250,
    limit: 20,
    loadingString: "Loading...",
    minInputLength: 3,
    rtl: false
  };

  if (typeof _settings === "undefined") {
    _settings = {};
  }
  const settings = Object.assign(_DEFAULTS, _settings);
  let list;
  let _currentItems;
  let _currentListEls;
  let _currentIndex = -1;

  function _debounce(func, delay, passFirst) {
    let timeout = null;

    return function debounced() {
      const context = this;
      const args = passFirst.concat([].slice.call(arguments));

      clearTimeout(timeout);
      timeout = setTimeout(function() {
        timeout = null;
        func.apply(context, args);
      }, delay);
    };
  }

  function _align(input) {
    const inputBox = input.getBoundingClientRect();
    list.style.minWidth = inputBox.width + "px";
    list.style.top = inputBox.bottom + "px";
    list.style.left = "auto"; // fixes width estimation
    let anchorRight = settings.rtl;
    if (!anchorRight && (
      inputBox.left + list.offsetWidth >=
      document.documentElement.offsetWidth
    )) {
      anchorRight = true;
    }
    if (anchorRight) {
      if (inputBox.right - list.offsetWidth <= 0) {
        anchorRight = false;
      }
    }
    if (anchorRight) {
      const posFromRight = document.documentElement.offsetWidth - inputBox.right;
      list.style.right = posFromRight + "px";
    } else {
      list.style.right = "auto";
      list.style.left = inputBox.left + "px";
    }
  }

  let lastInput = false;
  function _show(input) {
    lastInput = input;
    list.style.left = "-100%"; // hide offscreen
    list.classList.add("open");
  }

  function _hide(e) {
    if (
      typeof e !== "undefined" &&
      !!e.relatedTarget &&
      e.relatedTarget.hasAttribute("href")
    ) {
      return;
    }
    list.classList.remove("open");
    _currentIndex = -1;
    lastInput = false;
  }

  function _selectItem(item, input) {
    if (typeof item._disabled !== "undefined" && item._disabled) {
      return;
    }
    // Broadcast
    var event = document.createEvent("CustomEvent");
    // CustomEvent: name, canBubble, cancelable, detail
    event.initCustomEvent("ac-select", true, true, item);
    input.dispatchEvent(event);
    // Copy value
    if (typeof item === "string" || typeof item === "number") {
      input.value = item;
    } else if (typeof item.value === "undefined") {
      input.value = item.text;
    } else {
      input.value = item.value;
    }
    if (typeof item.href !== "undefined") {
      window.location.assign(item.href);
    }
    _hide();
  }

  function _renderItem(item, input) {
    let el = document.createElement("div");
    el.classList.add("ac-item");
    if (typeof item === "string" || typeof item === "number") {
      el.innerHTML = item;
    } else if (typeof item._header !== "undefined") {
      el.innerHTML = item._header;
      el.classList.add("ac-header");
      return el;
    } else {
      el.innerHTML = item.text;
      if (typeof item.sub !== "undefined") {
        let sub = document.createElement("small");
        sub.classList.add("ac-sub");
        sub.innerHTML = item.sub;
        el.appendChild(sub);
      }
      if (typeof item._disabled !== "undefined" && item._disabled) {
        el.setAttribute("disabled", true);
      }
    }
    el.addEventListener(
      "mousedown",
      e => {
        if (e.which === 1) {
          e.preventDefault();
          _selectItem(item, input);
        } else {
          return true;
        }
      },
      false
    );
    return el;
  }

  function _searchCallback(items, input) {
    if (items.length === 0) {
      _hide();
      return;
    }
    if (items.length > settings.limit) {
      items = items.slice(0, settings.limit);
    }
    _currentItems = items.slice();
    _currentListEls = items.map(item => _renderItem(item, input));
    list.innerHTML = "";
    _currentListEls.map(el => list.appendChild(el));
    _currentIndex = -1;
  }

  let lastCB;
  function _search(handler, input) {
    if (input.value.length < settings.minInputLength) {
      _hide();
      return;
    }
    let loadingEl = _renderItem({ _header: settings.loadingString });
    list.innerHTML = loadingEl.outerHTML;
    _show(input);
    _align(input);
    let thisCB = new Date().getTime();
    lastCB = thisCB;
    handler(input.value, function callback(items) {
      if (
        thisCB !== lastCB ||
        items === false ||
        items.length === 0
      ) {
        _hide();
        return;
      }
      _searchCallback(items, input);
      _align(input);
  function _keyup(handler, input, event) {
    // Ignore navigation keys
    // - Ignore control functions
    if (event.ctrlKey || event.which === 17) {
      return;
    }
    // - Function keys (F1 - F15)
    if (112 <= event.which && event.which <= 126) {
      return;
    }
    switch (event.which) {
      case 9: // tab
      case 13: // enter
      case 16: // shift
      case 20: // caps lock
      case 27: // esc
      case 33: // page up
      case 34: // page down
      case 35: // end
      case 36: // home
      case 37: // arrows
      case 38:
      case 39:
      case 40:
      case 45: // insert
      case 144: // num lock
      case 145: // scroll lock
      case 19: // pause/break
        return;
      default:
        _search(handler, input);
    }
  }

  function _keydown(handler, input, event) {
    // - Ignore control functions
    if (event.ctrlKey || event.which === 17) {
      return;
    }
    switch (event.which) {
      // arrow keys through items
      case 38: // up key
        event.preventDefault();
        if (_currentIndex === -1) {
          return;
        }
        _currentListEls[_currentIndex].classList.remove("is-selected");
        _currentIndex -= 1;
        if (_currentIndex === -1) {
          return;
        }
        _currentListEls[_currentIndex].classList.add("is-selected");
        break;
      case 40: // down key
        event.preventDefault();
        if (lastInput === false) {
          _search(handler, input);
          return;
        }
        if (_currentIndex === _currentItems.length - 1) {
          return;
        }
        if (_currentIndex > -1) {
          _currentListEls[_currentIndex].classList.remove("is-selected");
        }
        _currentIndex += 1;
        _currentListEls[_currentIndex].classList.add("is-selected");
        break;
      // enter to nav or populate
      case 9:
      case 13:
        if (_currentIndex === -1) {
          return;
        }
        event.preventDefault();
        _selectItem(_currentItems[_currentIndex], input);
        break;
      // hide on escape
      case 27:
        _hide();
        break;
    }
  }

  return function Factory(input, handler) {
    if (!input) {
      return false;
    }
    if (typeof handler === "undefined") {
      throw new Error(
        "Autocomplete needs handler to return items based on a query: function(query, callback) {}"
      );
    }

    // Create list element
    if (typeof list === "undefined") {
      list = document.querySelector(".autocomplete-results");
      if (!list) {
        list = document.createElement("div");
        list.classList.add("autocomplete-results");
        document.body.appendChild(list);
        window.addEventListener(
          "resize",
          function acresize() {
            if (lastInput === false) {
              return;
            }
            _align(lastInput);
          },
          false
        );
      }
    }

    // Activation / De-activation
    input.setAttribute("autocomplete", "off");
    input.addEventListener("focus", () => _search(handler, input), false);
    input.addEventListener("blur", _hide, false);

    // Input typing
    input.addEventListener(
      "keyup",
      _debounce(_keyup, settings.delay, [handler, input]),
      false
    );

    // Checking control characters
    input.addEventListener(
      "keydown",
      event => _keydown(handler, input, event),
      false
    );

    return input;
  };
}

if (typeof module !== "undefined") {
  module.exports = Autocomplete;
}
<commit_msg>Missed block for 6793d37<commit_after>/* https://github.com/vufind-org/autocomplete.js (v2.1.2) */
function Autocomplete(_settings) {
  const _DEFAULTS = {
    delay: 250,
    limit: 20,
    loadingString: "Loading...",
    minInputLength: 3,
    rtl: false
  };

  if (typeof _settings === "undefined") {
    _settings = {};
  }
  const settings = Object.assign(_DEFAULTS, _settings);
  let list;
  let _currentItems;
  let _currentListEls;
  let _currentIndex = -1;

  function _debounce(func, delay, passFirst) {
    let timeout = null;

    if (typeof passFirst === "undefined") {
      passFirst = [];
    }

    return function debounced() {
      const context = this;
      const args = passFirst.concat([].slice.call(arguments));

      clearTimeout(timeout);
      timeout = setTimeout(function() {
        timeout = null;
        func.apply(context, args);
      }, delay);
    };
  }

  function _align(input) {
    const inputBox = input.getBoundingClientRect();
    list.style.minWidth = inputBox.width + "px";
    list.style.top = inputBox.bottom + "px";
    list.style.left = "auto"; // fixes width estimation
    let anchorRight = settings.rtl;
    if (!anchorRight && (
      inputBox.left + list.offsetWidth >=
      document.documentElement.offsetWidth
    )) {
      anchorRight = true;
    }
    if (anchorRight) {
      if (inputBox.right - list.offsetWidth <= 0) {
        anchorRight = false;
      }
    }
    if (anchorRight) {
      const posFromRight = document.documentElement.offsetWidth - inputBox.right;
      list.style.right = posFromRight + "px";
    } else {
      list.style.right = "auto";
      list.style.left = inputBox.left + "px";
    }
  }

  let lastInput = false;
  function _show(input) {
    lastInput = input;
    list.style.left = "-100%"; // hide offscreen
    list.classList.add("open");
  }

  function _hide(e) {
    if (
      typeof e !== "undefined" &&
      !!e.relatedTarget &&
      e.relatedTarget.hasAttribute("href")
    ) {
      return;
    }
    list.classList.remove("open");
    _currentIndex = -1;
    lastInput = false;
  }

  function _selectItem(item, input) {
    if (typeof item._disabled !== "undefined" && item._disabled) {
      return;
    }
    // Broadcast
    var event = document.createEvent("CustomEvent");
    // CustomEvent: name, canBubble, cancelable, detail
    event.initCustomEvent("ac-select", true, true, item);
    input.dispatchEvent(event);
    // Copy value
    if (typeof item === "string" || typeof item === "number") {
      input.value = item;
    } else if (typeof item.value === "undefined") {
      input.value = item.text;
    } else {
      input.value = item.value;
    }
    if (typeof item.href !== "undefined") {
      window.location.assign(item.href);
    }
    _hide();
  }

  function _renderItem(item, input) {
    let el = document.createElement("div");
    el.classList.add("ac-item");
    if (typeof item === "string" || typeof item === "number") {
      el.innerHTML = item;
    } else if (typeof item._header !== "undefined") {
      el.innerHTML = item._header;
      el.classList.add("ac-header");
      return el;
    } else {
      el.innerHTML = item.text;
      if (typeof item.sub !== "undefined") {
        let sub = document.createElement("small");
        sub.classList.add("ac-sub");
        sub.innerHTML = item.sub;
        el.appendChild(sub);
      }
      if (typeof item._disabled !== "undefined" && item._disabled) {
        el.setAttribute("disabled", true);
      }
    }
    el.addEventListener(
      "mousedown",
      e => {
        if (e.which === 1) {
          e.preventDefault();
          _selectItem(item, input);
        } else {
          return true;
        }
      },
      false
    );
    return el;
  }

  function _searchCallback(items, input) {
    if (items.length > settings.limit) {
      items = items.slice(0, settings.limit);
    }
    _currentItems = items.slice();
    _currentListEls = items.map(item => _renderItem(item, input));
    list.innerHTML = "";
    _currentListEls.map(el => list.appendChild(el));
    _currentIndex = -1;
  }

  let lastCB;
  function _search(handler, input) {
    if (input.value.length < settings.minInputLength) {
      _hide();
      return;
    }
    let loadingEl = _renderItem({ _header: settings.loadingString });
    list.innerHTML = loadingEl.outerHTML;
    _show(input);
    _align(input);
    let thisCB = new Date().getTime();
    lastCB = thisCB;
    handler(input.value, function callback(items) {
      if (
        thisCB !== lastCB ||
        items === false ||
        items.length === 0
      ) {
        _hide();
        return;
      }
      _searchCallback(items, input);
      _align(input);
    });
  }

  function _keyup(handler, input, event) {
    // Ignore navigation keys
    // - Ignore control functions
    if (event.ctrlKey || event.which === 17) {
      return;
    }
    // - Function keys (F1 - F15)
    if (112 <= event.which && event.which <= 126) {
      return;
    }
    switch (event.which) {
      case 9: // tab
      case 13: // enter
      case 16: // shift
      case 20: // caps lock
      case 27: // esc
      case 33: // page up
      case 34: // page down
      case 35: // end
      case 36: // home
      case 37: // arrows
      case 38:
      case 39:
      case 40:
      case 45: // insert
      case 144: // num lock
      case 145: // scroll lock
      case 19: // pause/break
        return;
      default:
        _search(handler, input);
    }
  }

  function _keydown(handler, input, event) {
    // - Ignore control functions
    if (event.ctrlKey || event.which === 17) {
      return;
    }
    switch (event.which) {
      // arrow keys through items
      case 38: // up key
        event.preventDefault();
        if (_currentIndex === -1) {
          return;
        }
        _currentListEls[_currentIndex].classList.remove("is-selected");
        _currentIndex -= 1;
        if (_currentIndex === -1) {
          return;
        }
        _currentListEls[_currentIndex].classList.add("is-selected");
        break;
      case 40: // down key
        event.preventDefault();
        if (lastInput === false) {
          _search(handler, input);
          return;
        }
        if (_currentIndex === _currentItems.length - 1) {
          return;
        }
        if (_currentIndex > -1) {
          _currentListEls[_currentIndex].classList.remove("is-selected");
        }
        _currentIndex += 1;
        _currentListEls[_currentIndex].classList.add("is-selected");
        break;
      // enter to nav or populate
      case 9:
      case 13:
        if (_currentIndex === -1) {
          return;
        }
        event.preventDefault();
        _selectItem(_currentItems[_currentIndex], input);
        break;
      // hide on escape
      case 27:
        _hide();
        break;
    }
  }

  return function Factory(input, handler) {
    if (!input) {
      return false;
    }
    if (typeof handler === "undefined") {
      throw new Error(
        "Autocomplete needs handler to return items based on a query: function(query, callback) {}"
      );
    }

    // Create list element
    if (typeof list === "undefined") {
      list = document.querySelector(".autocomplete-results");
      if (!list) {
        list = document.createElement("div");
        list.classList.add("autocomplete-results");
        document.body.appendChild(list);
        window.addEventListener(
          "resize",
          function acresize() {
            if (lastInput === false) {
              return;
            }
            _align(lastInput);
          },
          false
        );
      }
    }

    // Activation / De-activation
    input.setAttribute("autocomplete", "off");
    input.addEventListener("focus", () => _search(handler, input), false);
    input.addEventListener("blur", _hide, false);

    // Input typing
    input.addEventListener(
      "keyup",
      _debounce(_keyup, settings.delay, [handler, input]),
      false
    );

    // Checking control characters
    input.addEventListener(
      "keydown",
      event => _keydown(handler, input, event),
      false
    );

    return input;
  };
}

if (typeof module !== "undefined") {
  module.exports = Autocomplete;
}
<|endoftext|>


<commit_before>URL_REGEX = "^(?:(?:http|https)\:\/\/)*(?:[a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,3}" +
    "(?:[a-zA-Z0-9]*)?\/?(?:[a-zA-Z0-9\-\._\?\,\'/\\\+&%\$#\=~])*" +
    "[^\.\,\)\(\s]$";

function show_error(error) {
  message = $("#message");
  message.html(error);
  message.removeClass("success");
  message.addClass("error");
}

function show_success() {
  message = $("#message");
  message.html("Saved successfully.");
  message.removeClass("error");
  message.addClass("success");
}

function restore_options() {
  chrome.storage.local.get(function(result) {
    if (chrome.extension.lastError == undefined) {
      $("#url").val(result["url"]);
      $("#first_only")[0].checked = result["first_only"];
    } else {
      error = "Settings could not be loaded. ("
          + chrome.extension.lastError + ")";
      show_error(error);
    }
  });
}

function validate_url(url) {
  re = new RegExp(URL_REGEX);
  return re.test(url);
}

function save_options() {
  if (!validate_url($("#url").val())) {
    show_error("Invalid URL");
    return;
  }

  options = {
    "url":$("#url").val(),
    "first_only":$("#first_only")[0].checked
  };
  chrome.storage.local.set(options, function() {
    if (chrome.extension.lastError == undefined) {
      show_success();
    } else {
      error = "Settings could not be saved. ("
          + chrome.extension.lastError + ")";
      show_error(error);
    }
  });
}

$(document).bind('DOMContentLoaded', restore_options);
$("#update").bind('click', save_options);
<commit_msg>Unified function naming convention<commit_after>URL_REGEX = "^(?:(?:http|https)\:\/\/)*(?:[a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,3}" +
    "(?:[a-zA-Z0-9]*)?\/?(?:[a-zA-Z0-9\-\._\?\,\'/\\\+&%\$#\=~])*" +
    "[^\.\,\)\(\s]$";

function showError(error) {
  message = $("#message");
  message.html(error);
  message.removeClass("success");
  message.addClass("error");
}

function show_success() {
  message = $("#message");
  message.html("Saved successfully.");
  message.removeClass("error");
  message.addClass("success");
}

function restoreOptions() {
  chrome.storage.local.get(function(result) {
    if (chrome.extension.lastError == undefined) {
      $("#url").val(result["url"]);
      $("#first_only")[0].checked = result["first_only"];
    } else {
      error = "Settings could not be loaded. ("
          + chrome.extension.lastError + ")";
      showError(error);
    }
  });
}

function validateUrl(url) {
  re = new RegExp(URL_REGEX);
  return re.test(url);
}

function saveOptions() {
  if (!validateUrl($("#url").val())) {
    showError("Invalid URL");
    return;
  }

  options = {
    "url":$("#url").val(),
    "first_only":$("#first_only")[0].checked
  };
  chrome.storage.local.set(options, function() {
    if (chrome.extension.lastError == undefined) {
      show_success();
    } else {
      error = "Settings could not be saved. ("
          + chrome.extension.lastError + ")";
      showError(error);
    }
  });
}

$(document).bind('DOMContentLoaded', restoreOptions);
$("#update").bind('click', saveOptions);
<|endoftext|>


<commit_before>#!/usr/bin/env node

// send new nodifier notifications to system notification-daemon
var config = require('./../../config/config.js');
var socket = require('socket.io-client').connect(config.host + ':' + config.port, {
	query: {token: config.token}
});
var spawn = require('child_process').spawn;

var launchDetached = function(program, args) {
	var child = spawn(program, args, {
		detached: true,
	});
	child.unref();
};

socket.on('newNotification', function(data) {
	var s = "";
	if(data.source) {
		s += data.source;
	}
	if(data.context) {
		if(data.source)
			s += ' ';
		s += '(' + data.context + ')';
	}

	s += ': ';
	s += data.text;

	launchDetached('notify-send', [s]);
	launchDetached('paplay', ['/usr/share/sounds/freedesktop/stereo/message.oga']);
});
<commit_msg>notify-send dunst title/content support<commit_after>#!/usr/bin/env node

// send new nodifier notifications to system notification-daemon
var config = require('./../../config/config.js');
var socket = require('socket.io-client').connect(config.host + ':' + config.port, {
	query: {token: config.token}
});
var spawn = require('child_process').spawn;

var launchDetached = function(program, args) {
	var child = spawn(program, args, {
		detached: true,
	});
	child.unref();
};

socket.on('newNotification', function(data) {
	var title = "";
	if(data.source) {
		title += data.source;
	}
	if(data.context) {
		if(data.source)
			title += ' ';
		title += '(' + data.context + ')';
	}

	launchDetached('notify-send', [title, data.text]);
	launchDetached('paplay', ['/usr/share/sounds/freedesktop/stereo/message.oga']);
});
<|endoftext|>


<commit_before>"use strict";

const pod_list = ["iliketoast.net", "diaspora-fr.org", "socializer.cc", "diaspora.podzimek.org", "social.mrzyx.de", "diasporabr.com.br", "pod.jpope.org", "diapod.net", "diaspora.lotz.li", "pod.fulll.name", "pod.geraspora.de", "nerdpol.ch", "jenaspora.de", "diaspora.koehn.com", "diasp.de", "diasp.eu", "sechat.org", "pod.mieth.net", "pod.orkz.net", "social.mbuto.me", "dia.manuelbichler.at", "diapod.org", "joindiaspora.com", "ruhrspora.de", "nx-pod.de", "pirati.ca", "diaspora.net.gr", "diasp.org", "despora.de", "poddery.com", "diaspora.lebarjack.com", "co.zy.lc", "cryptospora.net", "social.cooleysekula.net", "diasp.cz", "friendica.libertypod.com", "tryfriendica.de", "snarl.de", "mondiaspora.net", "diasp.eu.com", "diaspora.permutationsofchaos.com", "diaspora.punkbeer.me", "wk3.org", "social.incenp.org", "diaspora.calenhad.com", "pod.asap-soft.com", "flokk.no", "kapok.se", "diasporabrazil.org", "datataffel.dk", "diaspora-malayalam.rhcloud.com", "f.tschlotfeldt.de", "diaspora.espiritolivre.org", "pod.omgsrsly.net", "d.consumium.org", "social.daxbau.net", "pod.hoizi.net", "framasphere.org", "social.sfl.pro.br", "friendica.dszdw.net", "ns2.libertypod.net", "sysad.org", "russiandiaspora.org", "squeet.me", "shrekislove.us", "friendica.a-zwenkau.de", "diaspora.mokrynskyi.com", "diaspora.hzsogood.net", "pod.cyano.at", "mondiaspora.org", "pod.nomorestars.com", "share.naturalnews.com", "diaspora.mobi42.de", "canfly.org", "kirgroup.com", "libertypod.com", "abcentric.net", "red.zottel.net", "nostupid.zone", "diaspora.openhosting.com", "federated.social", "wgserv.eu", "diasp.nl", "red.elsmussols.net", "social.teia.bio.br", "diaspora.soh.re", "failure.net", "thinkopen.net", "friendica.kommune4.de", "toktan.org", "friendika.me4.it", "friendica.eu", "karl.marx.pm", "theshi.re", "social.lanham.id.au", "pod.ponk.pink", "redtest.dszdw.net", "pod.efence.co.uk", "podbay.net", "diaspora.zenfolie.org", "diaspora.com.ar", "librenet.gr", "red.wf", "pod.4ray.co", "jons.gr", "pod.pc-tiede.de", "my.federated.social", "diaspora.psyco.fr", "pubpod.alqualonde.org", "rootpod.org", "red.vilarejo.pro.br", "social.elaon.de", "redmatrix.netzspielplatz.de", "driaans.nl", "diaspora.moosje.nl", "diasp.in", "pod.thomasdalichow.de", "www.maukin.cz", "spora.zone", "ohai.nl", "diaspote.org", "pod.promedol.com", "libranet.de", "pod.itabs.nl", "pod.8n1.org", "networkwizard.de", "matrix.0xaf.org", "diasp.ca", "redmatrix.info", "pod.ismus.net", "pod.datenknoten.me", "pod.tchncs.de", "diaspora.bohramt.de", "friendica.tk", "tupambae.org", "berdaguermontes.eu", "berlinspora.de", "diaspora.ddh.nl", "diasporadevtiec.herokuapp.com", "diaspora.digi-merc.org", "diaspora.sceal.ie", "diaspora.subsignal.org", "diaspora.trash-talk.de", "diaspora.treefish.org", "diasp.ot-si.com", "pod.brief.guru", "pod.ferner-online.de", "pod.g3l.org", "pod.sd.vc", "pod.volt.io", "revreso.de", "sn.tdracer.de", "thecrimsontint.com", "urbain.es", "www.diasporaix.de", "diaspora.schlimme.net", "pod.psynet.su", "sirius-dstar.rhcloud.com", "catpod.cat.scot", "pod.storel.li", "redmatrix.mm82.spica.uberspace.de", "diaspora.lawri.nl", "pod.thing.org", "diasp.grin.hu", "pod.devosi.org", "liberdade.digital", "socialpod.us", "dia.myocastor.de", "diaspora.laka.lv", "pod.globale-gruppe.de", "podricing.pw", "meus.herokuapp.com", "pod.braitbart.net", "friendica.anonsys.net", "f.praschnig.com", "pod.disroot.org", "joindiaspora.es", "pod.joindiaspora.es", "espora.com.es", "www.15o2.de", "pod.chabotsi.fr", "kentshikama.com", "therealtalk.org", "diasp.pl", "diaspora.horwood.biz", "hubzilla.zottel.net", "friendica.xyz", "fr.got-tty.org", "pod.diaspora.software", "pod.strugee.net", "social.baldr.io", "diasp.danletard.de", "diaspora.your-platform.org", "hubz.gacrux.uberspace.de", "forum.tryfriendica.de", "lastauth.com", "hubzilla.site", "aegibson.me", "darkside.rocks", "blablanet.com", "pod.sccn.club", "jewishchron.com", "cats.pm", "www.librenet.gr", "www.datataffel.dk", "club.gohlke.nrw", "diaspora.raven-ip.com", "diaspora.kosebamse.com", "diaspora.afsedu.eu", "lstoll-diaspora.herokuapp.com", "gesichtsbu.ch", "blablanet.es", "blablanet.fr", "blablanet.ru", "nerdica.net", "diaspora.pingupod.de", "pod.qarte.de", "pod.cyberdungeon.de", "pod.libreplanetbr.org", "subvillage.de", "diaspora.poleni.com", "diaspora.freedombox.rocks", "pod.dapor.net", "hub.vilarejo.pro.br", "monkeystew.social", "diaspod.de", "pod.liebeleu.de", "15o2.de", "blablanet.it", "netzwerk.transition-regensburg.de", "diaspora.animalnet.de", "f.haeder.net", "diaspora.kapper.net", "hubzilla.tk", "hubzilla.top", "witchcraft.ml", "hubzilla.ovh", "hubzilla.link", "hubzilla.click", "piratenpartij.org", "diaspora.snakenode.eu", "diaspora.ai.it.hs-worms.de", "got.noip.me", "hub.wuewuewue.de", "hubzilla.a-zwenkau.de", "hub.consumium.org", "freehuman.fr", "pod.dirkomatik.de", "hubzilla.cacaogrid.org", "pod.alterworld.info", "pod.hashtagueule.fr", "espora.social", "zomaareenfeestje.org", "hz.oavb.eu", "teamhubzilla.org", "hz.macgirvin.com", "relay.iliketoast.net", "zot.datamol.in", "pe.spbstu.ru", "hub.transition-regensburg.de", "hubzilla-kingemir.rhcloud.com", "whatsnewz.com", "friendica.zavb.de", "pod.sertelon.fr", "privysocial.com", "laba.mba", "hub.libranet.de", "social.acclaro.digital", "friendica.sonatagreen.com", "blog.serverok.pl", "jasonrobinson.me", "hubzilla.zavb.de", "grindcore.ch", "hubzilla.fkn-systems.de", "diaspora.aeriesguard.com", "social.yl.ms", "social.azkware.net", "hubzilla.nomadicista.org", "0kcg.com", "blablanet.co", "pod.dukun.de", "diaspora.u4u.org", "copains.du-coin.fr", "social.piratenpartei.koeln", "friendica.myw3.de", "friendica.mrpetovan.com", "jardin.umaneti.net", "hubzilla.mrpetovan.com", "diasp.e-lehmann.de", "diaspora.microdata.co.uk", "diasporapod.no", "librenet.co.za", "tippentappen.de", "diaspora.hofud.com", "diasporing.ch", "f.left.social", "thesocialnerd.online", "diaspora.axel-faure.eu", "confetticake.club", "friendica.acclaro.digital", "hub.somaton.com", "villianbook.com", "diaspora.zone", "redmatrix.us", "hochminuseins.net", "pod.trento.be", "pod.undernet.uy", "horche.demkontinuum.de", "pod.jeweet.net", "friends.tausys.de", "diaspora.gemeinsam.jetzt", "mmkr.co", "friendica.hubup.pro", "hub.stephenson.cc", "albigro.com", "mh8.lat-clan.fr", "diaspora.softwarelivre.org", "pod.mew.cat", "hubz.secretlair.me", "steamworks.se", "pod.kneedrag.org", "fr.zy.lc", "blablanet.click", "meld.de", "philos.fkn-systems.de"];
<commit_msg>Replace my old pod with the new one [spora.grin.hu]<commit_after>"use strict";

const pod_list = ["iliketoast.net", "diaspora-fr.org", "socializer.cc", "diaspora.podzimek.org", "social.mrzyx.de", "diasporabr.com.br", "pod.jpope.org", "diapod.net", "diaspora.lotz.li", "pod.fulll.name", "pod.geraspora.de", "nerdpol.ch", "jenaspora.de", "diaspora.koehn.com", "diasp.de", "diasp.eu", "sechat.org", "pod.mieth.net", "pod.orkz.net", "social.mbuto.me", "dia.manuelbichler.at", "diapod.org", "joindiaspora.com", "ruhrspora.de", "nx-pod.de", "pirati.ca", "diaspora.net.gr", "diasp.org", "despora.de", "poddery.com", "diaspora.lebarjack.com", "co.zy.lc", "cryptospora.net", "social.cooleysekula.net", "diasp.cz", "friendica.libertypod.com", "tryfriendica.de", "snarl.de", "mondiaspora.net", "diasp.eu.com", "diaspora.permutationsofchaos.com", "diaspora.punkbeer.me", "wk3.org", "social.incenp.org", "diaspora.calenhad.com", "pod.asap-soft.com", "flokk.no", "kapok.se", "diasporabrazil.org", "datataffel.dk", "diaspora-malayalam.rhcloud.com", "f.tschlotfeldt.de", "diaspora.espiritolivre.org", "pod.omgsrsly.net", "d.consumium.org", "social.daxbau.net", "pod.hoizi.net", "framasphere.org", "social.sfl.pro.br", "friendica.dszdw.net", "ns2.libertypod.net", "sysad.org", "russiandiaspora.org", "squeet.me", "shrekislove.us", "friendica.a-zwenkau.de", "diaspora.mokrynskyi.com", "diaspora.hzsogood.net", "pod.cyano.at", "mondiaspora.org", "pod.nomorestars.com", "share.naturalnews.com", "diaspora.mobi42.de", "canfly.org", "kirgroup.com", "libertypod.com", "abcentric.net", "red.zottel.net", "nostupid.zone", "diaspora.openhosting.com", "federated.social", "wgserv.eu", "diasp.nl", "red.elsmussols.net", "social.teia.bio.br", "diaspora.soh.re", "failure.net", "thinkopen.net", "friendica.kommune4.de", "toktan.org", "friendika.me4.it", "friendica.eu", "karl.marx.pm", "theshi.re", "social.lanham.id.au", "pod.ponk.pink", "redtest.dszdw.net", "pod.efence.co.uk", "podbay.net", "diaspora.zenfolie.org", "diaspora.com.ar", "librenet.gr", "red.wf", "pod.4ray.co", "jons.gr", "pod.pc-tiede.de", "my.federated.social", "diaspora.psyco.fr", "pubpod.alqualonde.org", "rootpod.org", "red.vilarejo.pro.br", "social.elaon.de", "redmatrix.netzspielplatz.de", "driaans.nl", "diaspora.moosje.nl", "diasp.in", "pod.thomasdalichow.de", "www.maukin.cz", "spora.zone", "ohai.nl", "diaspote.org", "pod.promedol.com", "libranet.de", "pod.itabs.nl", "pod.8n1.org", "networkwizard.de", "matrix.0xaf.org", "diasp.ca", "redmatrix.info", "pod.ismus.net", "pod.datenknoten.me", "pod.tchncs.de", "diaspora.bohramt.de", "friendica.tk", "tupambae.org", "berdaguermontes.eu", "berlinspora.de", "diaspora.ddh.nl", "diasporadevtiec.herokuapp.com", "diaspora.digi-merc.org", "diaspora.sceal.ie", "diaspora.subsignal.org", "diaspora.trash-talk.de", "diaspora.treefish.org", "diasp.ot-si.com", "pod.brief.guru", "pod.ferner-online.de", "pod.g3l.org", "pod.sd.vc", "pod.volt.io", "revreso.de", "sn.tdracer.de", "thecrimsontint.com", "urbain.es", "www.diasporaix.de", "diaspora.schlimme.net", "pod.psynet.su", "sirius-dstar.rhcloud.com", "catpod.cat.scot", "pod.storel.li", "redmatrix.mm82.spica.uberspace.de", "diaspora.lawri.nl", "pod.thing.org", "spora.grin.hu", "pod.devosi.org", "liberdade.digital", "socialpod.us", "dia.myocastor.de", "diaspora.laka.lv", "pod.globale-gruppe.de", "podricing.pw", "meus.herokuapp.com", "pod.braitbart.net", "friendica.anonsys.net", "f.praschnig.com", "pod.disroot.org", "joindiaspora.es", "pod.joindiaspora.es", "espora.com.es", "www.15o2.de", "pod.chabotsi.fr", "kentshikama.com", "therealtalk.org", "diasp.pl", "diaspora.horwood.biz", "hubzilla.zottel.net", "friendica.xyz", "fr.got-tty.org", "pod.diaspora.software", "pod.strugee.net", "social.baldr.io", "diasp.danletard.de", "diaspora.your-platform.org", "hubz.gacrux.uberspace.de", "forum.tryfriendica.de", "lastauth.com", "hubzilla.site", "aegibson.me", "darkside.rocks", "blablanet.com", "pod.sccn.club", "jewishchron.com", "cats.pm", "www.librenet.gr", "www.datataffel.dk", "club.gohlke.nrw", "diaspora.raven-ip.com", "diaspora.kosebamse.com", "diaspora.afsedu.eu", "lstoll-diaspora.herokuapp.com", "gesichtsbu.ch", "blablanet.es", "blablanet.fr", "blablanet.ru", "nerdica.net", "diaspora.pingupod.de", "pod.qarte.de", "pod.cyberdungeon.de", "pod.libreplanetbr.org", "subvillage.de", "diaspora.poleni.com", "diaspora.freedombox.rocks", "pod.dapor.net", "hub.vilarejo.pro.br", "monkeystew.social", "diaspod.de", "pod.liebeleu.de", "15o2.de", "blablanet.it", "netzwerk.transition-regensburg.de", "diaspora.animalnet.de", "f.haeder.net", "diaspora.kapper.net", "hubzilla.tk", "hubzilla.top", "witchcraft.ml", "hubzilla.ovh", "hubzilla.link", "hubzilla.click", "piratenpartij.org", "diaspora.snakenode.eu", "diaspora.ai.it.hs-worms.de", "got.noip.me", "hub.wuewuewue.de", "hubzilla.a-zwenkau.de", "hub.consumium.org", "freehuman.fr", "pod.dirkomatik.de", "hubzilla.cacaogrid.org", "pod.alterworld.info", "pod.hashtagueule.fr", "espora.social", "zomaareenfeestje.org", "hz.oavb.eu", "teamhubzilla.org", "hz.macgirvin.com", "relay.iliketoast.net", "zot.datamol.in", "pe.spbstu.ru", "hub.transition-regensburg.de", "hubzilla-kingemir.rhcloud.com", "whatsnewz.com", "friendica.zavb.de", "pod.sertelon.fr", "privysocial.com", "laba.mba", "hub.libranet.de", "social.acclaro.digital", "friendica.sonatagreen.com", "blog.serverok.pl", "jasonrobinson.me", "hubzilla.zavb.de", "grindcore.ch", "hubzilla.fkn-systems.de", "diaspora.aeriesguard.com", "social.yl.ms", "social.azkware.net", "hubzilla.nomadicista.org", "0kcg.com", "blablanet.co", "pod.dukun.de", "diaspora.u4u.org", "copains.du-coin.fr", "social.piratenpartei.koeln", "friendica.myw3.de", "friendica.mrpetovan.com", "jardin.umaneti.net", "hubzilla.mrpetovan.com", "diasp.e-lehmann.de", "diaspora.microdata.co.uk", "diasporapod.no", "librenet.co.za", "tippentappen.de", "diaspora.hofud.com", "diasporing.ch", "f.left.social", "thesocialnerd.online", "diaspora.axel-faure.eu", "confetticake.club", "friendica.acclaro.digital", "hub.somaton.com", "villianbook.com", "diaspora.zone", "redmatrix.us", "hochminuseins.net", "pod.trento.be", "pod.undernet.uy", "horche.demkontinuum.de", "pod.jeweet.net", "friends.tausys.de", "diaspora.gemeinsam.jetzt", "mmkr.co", "friendica.hubup.pro", "hub.stephenson.cc", "albigro.com", "mh8.lat-clan.fr", "diaspora.softwarelivre.org", "pod.mew.cat", "hubz.secretlair.me", "steamworks.se", "pod.kneedrag.org", "fr.zy.lc", "blablanet.click", "meld.de", "philos.fkn-systems.de"];
<|endoftext|>