<commit_before>import alt from "alt-instance";
import SettingsActions from "actions/SettingsActions";
import IntlActions from "actions/IntlActions";
import Immutable from "immutable";
import {merge} from "lodash";
import ls from "common/localStorage";
import { Apis } from "bitsharesjs-ws";
import { settingsAPIs } from "api/apiConfig";

const CORE_ASSET = "BTS"; // Setting this to BTS to prevent loading issues when used with BTS chain which is the most usual case currently

const STORAGE_KEY = "__graphene__";
let ss = new ls(STORAGE_KEY);

class SettingsStore {
    constructor() {
        this.exportPublicMethods({
            init: this.init.bind(this),
            getSetting: this.getSetting.bind(this),
            getLastBudgetObject: this.getLastBudgetObject.bind(this),
            setLastBudgetObject: this.setLastBudgetObject.bind(this)
        });

        this.bindListeners({
            onChangeSetting: SettingsActions.changeSetting,
            onChangeViewSetting: SettingsActions.changeViewSetting,
            onChangeMarketDirection: SettingsActions.changeMarketDirection,
            onAddStarMarket: SettingsActions.addStarMarket,
            onRemoveStarMarket: SettingsActions.removeStarMarket,
            onClearStarredMarkets: SettingsActions.clearStarredMarkets,
            onAddWS: SettingsActions.addWS,
            onRemoveWS: SettingsActions.removeWS,
            onHideAsset: SettingsActions.hideAsset,
            onClearSettings: SettingsActions.clearSettings,
            onSwitchLocale: IntlActions.switchLocale,
            onSetUserMarket: SettingsActions.setUserMarket,
            onUpdateLatencies: SettingsActions.updateLatencies
        });

        this.initDone = false;
        this.defaultSettings = Immutable.Map({
            locale: "en",
            apiServer: settingsAPIs.DEFAULT_WS_NODE,
            faucet_address: settingsAPIs.DEFAULT_FAUCET,
            unit: CORE_ASSET,
            showSettles: false,
            showAssetPercent: false,
            walletLockTimeout: 60 * 10,
            themes: "darkTheme",
            passwordLogin: true
        });

        // If you want a default value to be translated, add the translation to settings in locale-xx.js
        // and use an object {translate: key} in the defaults array
        let apiServer = settingsAPIs.WS_NODE_LIST;

        let defaults = {
            locale: [
                "en",
                "zh",
                "fr",
                "ko",
                "de",
                "es",
                "it",
                "tr",
                "ru"
            ],
            apiServer: apiServer,
            unit: [
                CORE_ASSET,
                "USD",
                "CNY",
                "BTC",
                "EUR",
                "GBP"
            ],
            showSettles: [
                {translate: "yes"},
                {translate: "no"}
            ],
            showAssetPercent: [
                {translate: "yes"},
                {translate: "no"}
            ],
            themes: [
                "darkTheme",
                "lightTheme",
                "midnightTheme"
            ],
            passwordLogin: [
                {translate: "cloud_login"},
                {translate: "local_wallet"}
            ]
            // confirmMarketOrder: [
            //     {translate: "confirm_yes"},
            //     {translate: "confirm_no"}
            // ]
        };

        this.settings = Immutable.Map(merge(this.defaultSettings.toJS(), ss.get("settings_v3")));
        if (this.settings.get("themes") === "olDarkTheme") {
            this.settings = this.settings.set("themes", "midnightTheme");
        }
        let savedDefaults = ss.get("defaults_v1", {});
        /* Fix for old clients after changing cn to zh */
        if (savedDefaults && savedDefaults.locale) {
            let cnIdx = savedDefaults.locale.findIndex(a => a === "cn");
            if (cnIdx !== -1) savedDefaults.locale[cnIdx] = "zh";
        }
        if (savedDefaults.apiServer) {
            savedDefaults.apiServer = savedDefaults.apiServer.filter(a => {
                return !defaults.apiServer.find(b => {
                    return b.url === a.url;
                });
            });
        }
        this.defaults = merge({}, defaults, savedDefaults);

        (savedDefaults.apiServer || []).forEach(api => {
            let hasApi = false;
            if (typeof api === "string") {
                api = {url: api, location: null};
            }
            this.defaults.apiServer.forEach(server => {
                if (server.url === api.url) {
                    hasApi = true;
                }
            });

            if (!hasApi) {
                this.defaults.apiServer.push(api);
            }
        });

        if (!savedDefaults || (savedDefaults && (!savedDefaults.apiServer || !savedDefaults.apiServer.length))) {
            for (let i = apiServer.length - 1; i >= 0; i--) {
                let hasApi = false;
                this.defaults.apiServer.forEach(api => {
                    if (api.url === apiServer[i].url) {
                        hasApi = true;
                    }
                });
                if (!hasApi) {
                    this.defaults.apiServer.unshift(apiServer[i]);
                }
            }
        }

        this.viewSettings = Immutable.Map(ss.get("viewSettings_v1"));

        this.marketDirections = Immutable.Map(ss.get("marketDirections"));

        this.hiddenAssets = Immutable.List(ss.get("hiddenAssets", []));

        this.apiLatencies = ss.get("apiLatencies", {});

        this.mainnet_faucet = ss.get("mainnet_faucet", settingsAPIs.DEFAULT_FAUCET);
        this.testnet_faucet = ss.get("testnet_faucet", settingsAPIs.TESTNET_FAUCET);
    }

    init() {
        return new Promise((resolve) => {
            if (this.initDone) resolve();
            this.starredKey = this._getChainKey("markets");
            this.marketsKey = this._getChainKey("userMarkets");
            // Default markets setup
            let topMarkets = {
                markets_4018d784: [ // BTS MAIN NET
                    "OPEN.MKR", "BTS", "OPEN.ETH", "ICOO", "BTC", "OPEN.LISK", "BKT",
                    "OPEN.STEEM", "OPEN.GAME", "OCT", "USD", "CNY", "BTSR", "OBITS",
                    "OPEN.DGD", "EUR", "GOLD", "SILVER", "IOU.CNY", "OPEN.DASH",
                    "OPEN.USDT", "OPEN.EURT", "OPEN.BTC", "CADASTRAL", "BLOCKPAY", "BTWTY",
                    "OPEN.INCNT", "KAPITAL", "OPEN.MAID", "OPEN.SBD", "OPEN.GRC",
                    "YOYOW", "HERO", "RUBLE", "SMOKE", "STEALTH", "BRIDGE.BCO",
                    "BRIDGE.BTC", "KEXCOIN", "PPY", "OPEN.EOS", "OPEN.OMG", "CVCOIN",
                    "BRIDGE.ZNY", "BRIDGE.MONA", "BRIDGE.BCO", "OPEN.LTC", "GDEX.BTC"
                ],
                markets_39f5e2ed: [ // TESTNET
                    "PEG.FAKEUSD", "BTWTY"
                ]
            };

            let bases = {
                markets_4018d784: [ // BTS MAIN NET
                    "USD", "OPEN.BTC", "CNY", "BTS", "BTC"
                ],
                markets_39f5e2ed: [ // TESTNET
                    "TEST"
                ]
            };

            let coreAssets = {markets_4018d784: "BTS", markets_39f5e2ed: "TEST"};
            let coreAsset = coreAssets[this.starredKey] || "BTS";
            this.defaults.unit[0] = coreAsset;

            let chainBases = bases[this.starredKey] || bases.markets_4018d784;
            this.preferredBases = Immutable.List(chainBases);

            function addMarkets(target, base, markets) {
                markets.filter(a => {
                    return a !== base;
                }).forEach(market => {
                    target.push([`${market}_${base}`, {"quote": market,"base": base}]);
                });
            }

            let defaultMarkets = [];
            let chainMarkets = topMarkets[this.starredKey] || [];
            this.preferredBases.forEach(base => {
                addMarkets(defaultMarkets, base, chainMarkets);
            });

            this.defaultMarkets = Immutable.Map(defaultMarkets);
            this.starredMarkets = Immutable.Map(ss.get(this.starredKey, []));
            this.userMarkets = Immutable.Map(ss.get(this.marketsKey, {}));

            this.initDone = true;
            resolve();
        });
    }

    getSetting(setting) {
        return this.settings.get(setting);
    }

    onChangeSetting(payload) {
        this.settings = this.settings.set(
            payload.setting,
            payload.value
        );

        switch(payload.setting) {
            case "faucet_address":
                if (payload.value.indexOf("testnet") === -1) {
                    this.mainnet_faucet = payload.value;
                    ss.set("mainnet_faucet", payload.value);
                } else {
                    this.testnet_faucet = payload.value;
                    ss.set("testnet_faucet", payload.value);
                }
                break;

            case "apiServer":
                let faucetUrl = payload.value.indexOf("testnet") !== -1 ?
                    this.testnet_faucet : this.mainnet_faucet;
                this.settings = this.settings.set(
                    "faucet_address",
                    faucetUrl
                );
                break;

            case "walletLockTimeout":
                ss.set("lockTimeout", payload.value);
                break;

            default:
                break;
        }

        ss.set("settings_v3", this.settings.toJS());
    }

    onChangeViewSetting(payload) {
        for (let key in payload) {
            this.viewSettings = this.viewSettings.set(key, payload[key]);
        }

        ss.set("viewSettings_v1", this.viewSettings.toJS());
    }

    onChangeMarketDirection(payload) {
        for (let key in payload) {
            this.marketDirections = this.marketDirections.set(key, payload[key]);
        }
        ss.set("marketDirections", this.marketDirections.toJS());
    }

    onHideAsset(payload) {
        if (payload.id) {
            if (!payload.status) {
                this.hiddenAssets = this.hiddenAssets.delete(this.hiddenAssets.indexOf(payload.id));
            } else {
                this.hiddenAssets = this.hiddenAssets.push(payload.id);
            }
        }

        ss.set("hiddenAssets", this.hiddenAssets.toJS());
    }

    onAddStarMarket(market) {
        let marketID = market.quote + "_" + market.base;
        if (!this.starredMarkets.has(marketID)) {
            this.starredMarkets = this.starredMarkets.set(marketID, {quote: market.quote, base: market.base});

            ss.set(this.starredKey, this.starredMarkets.toJS());
        } else {
            return false;
        }
    }

    onSetUserMarket(payload) {
        let marketID = payload.quote + "_" + payload.base;
        if (payload.value) {
            this.userMarkets = this.userMarkets.set(marketID, {quote: payload.quote, base: payload.base});
        } else {
            this.userMarkets = this.userMarkets.delete(marketID);
        }
        ss.set(this.marketsKey, this.userMarkets.toJS());
    }

    onRemoveStarMarket(market) {
        let marketID = market.quote + "_" + market.base;

        this.starredMarkets = this.starredMarkets.delete(marketID);

        ss.set(this.starredKey, this.starredMarkets.toJS());
    }

    onClearStarredMarkets(){
        this.starredMarkets = Immutable.Map({});
        ss.set(this.starredKey, this.starredMarkets.toJS());
    }

    onAddWS(ws) {
        if (typeof ws === "string") {
            ws = {url: ws, location: null};
        }
        this.defaults.apiServer.push(ws);
        ss.set("defaults_v1", this.defaults);
    }

    onRemoveWS(index) {
        this.defaults.apiServer.splice(index, 1);
        ss.set("defaults_v1", this.defaults);
    }

    onClearSettings(resolve) {
        ss.remove("settings_v3");
        this.settings = this.defaultSettings;

        ss.set("settings_v3", this.settings.toJS());

        if (resolve) {
            resolve();
        }
    }

    onSwitchLocale({locale}) {
        this.onChangeSetting({setting: "locale", value: locale});
    }

    _getChainKey(key) {
        const chainId = Apis.instance().chain_id;
        return key + (chainId ? `_${chainId.substr(0, 8)}` : "");
    }

    onUpdateLatencies(latencies) {
        ss.set("apiLatencies", latencies);
        this.apiLatencies = latencies;
    }

    getLastBudgetObject() {
        return ss.get(this._getChainKey("lastBudgetObject"), "2.13.1");
    }

    setLastBudgetObject(value) {
        ss.set(this._getChainKey("lastBudgetObject"), value);
    }
}

export default alt.createStore(SettingsStore, "SettingsStore");
<commit_msg>delete asset to fix merge issue<commit_after>import alt from "alt-instance";
import SettingsActions from "actions/SettingsActions";
import IntlActions from "actions/IntlActions";
import Immutable from "immutable";
import {merge} from "lodash";
import ls from "common/localStorage";
import { Apis } from "bitsharesjs-ws";
import { settingsAPIs } from "api/apiConfig";

const CORE_ASSET = "BTS"; // Setting this to BTS to prevent loading issues when used with BTS chain which is the most usual case currently

const STORAGE_KEY = "__graphene__";
let ss = new ls(STORAGE_KEY);

class SettingsStore {
    constructor() {
        this.exportPublicMethods({
            init: this.init.bind(this),
            getSetting: this.getSetting.bind(this),
            getLastBudgetObject: this.getLastBudgetObject.bind(this),
            setLastBudgetObject: this.setLastBudgetObject.bind(this)
        });

        this.bindListeners({
            onChangeSetting: SettingsActions.changeSetting,
            onChangeViewSetting: SettingsActions.changeViewSetting,
            onChangeMarketDirection: SettingsActions.changeMarketDirection,
            onAddStarMarket: SettingsActions.addStarMarket,
            onRemoveStarMarket: SettingsActions.removeStarMarket,
            onClearStarredMarkets: SettingsActions.clearStarredMarkets,
            onAddWS: SettingsActions.addWS,
            onRemoveWS: SettingsActions.removeWS,
            onHideAsset: SettingsActions.hideAsset,
            onClearSettings: SettingsActions.clearSettings,
            onSwitchLocale: IntlActions.switchLocale,
            onSetUserMarket: SettingsActions.setUserMarket,
            onUpdateLatencies: SettingsActions.updateLatencies
        });

        this.initDone = false;
        this.defaultSettings = Immutable.Map({
            locale: "en",
            apiServer: settingsAPIs.DEFAULT_WS_NODE,
            faucet_address: settingsAPIs.DEFAULT_FAUCET,
            unit: CORE_ASSET,
            showSettles: false,
            showAssetPercent: false,
            walletLockTimeout: 60 * 10,
            themes: "darkTheme",
            passwordLogin: true
        });

        // If you want a default value to be translated, add the translation to settings in locale-xx.js
        // and use an object {translate: key} in the defaults array
        let apiServer = settingsAPIs.WS_NODE_LIST;

        let defaults = {
            locale: [
                "en",
                "zh",
                "fr",
                "ko",
                "de",
                "es",
                "it",
                "tr",
                "ru"
            ],
            apiServer: apiServer,
            unit: [
                CORE_ASSET,
                "USD",
                "CNY",
                "BTC",
                "EUR",
                "GBP"
            ],
            showSettles: [
                {translate: "yes"},
                {translate: "no"}
            ],
            showAssetPercent: [
                {translate: "yes"},
                {translate: "no"}
            ],
            themes: [
                "darkTheme",
                "lightTheme",
                "midnightTheme"
            ],
            passwordLogin: [
                {translate: "cloud_login"},
                {translate: "local_wallet"}
            ]
            // confirmMarketOrder: [
            //     {translate: "confirm_yes"},
            //     {translate: "confirm_no"}
            // ]
        };

        this.settings = Immutable.Map(merge(this.defaultSettings.toJS(), ss.get("settings_v3")));
        if (this.settings.get("themes") === "olDarkTheme") {
            this.settings = this.settings.set("themes", "midnightTheme");
        }
        let savedDefaults = ss.get("defaults_v1", {});
        /* Fix for old clients after changing cn to zh */
        if (savedDefaults && savedDefaults.locale) {
            let cnIdx = savedDefaults.locale.findIndex(a => a === "cn");
            if (cnIdx !== -1) savedDefaults.locale[cnIdx] = "zh";
        }
        if (savedDefaults.apiServer) {
            savedDefaults.apiServer = savedDefaults.apiServer.filter(a => {
                return !defaults.apiServer.find(b => {
                    return b.url === a.url;
                });
            });
        }
        this.defaults = merge({}, defaults, savedDefaults);

        (savedDefaults.apiServer || []).forEach(api => {
            let hasApi = false;
            if (typeof api === "string") {
                api = {url: api, location: null};
            }
            this.defaults.apiServer.forEach(server => {
                if (server.url === api.url) {
                    hasApi = true;
                }
            });

            if (!hasApi) {
                this.defaults.apiServer.push(api);
            }
        });

        if (!savedDefaults || (savedDefaults && (!savedDefaults.apiServer || !savedDefaults.apiServer.length))) {
            for (let i = apiServer.length - 1; i >= 0; i--) {
                let hasApi = false;
                this.defaults.apiServer.forEach(api => {
                    if (api.url === apiServer[i].url) {
                        hasApi = true;
                    }
                });
                if (!hasApi) {
                    this.defaults.apiServer.unshift(apiServer[i]);
                }
            }
        }

        this.viewSettings = Immutable.Map(ss.get("viewSettings_v1"));

        this.marketDirections = Immutable.Map(ss.get("marketDirections"));

        this.hiddenAssets = Immutable.List(ss.get("hiddenAssets", []));

        this.apiLatencies = ss.get("apiLatencies", {});

        this.mainnet_faucet = ss.get("mainnet_faucet", settingsAPIs.DEFAULT_FAUCET);
        this.testnet_faucet = ss.get("testnet_faucet", settingsAPIs.TESTNET_FAUCET);
    }

    init() {
        return new Promise((resolve) => {
            if (this.initDone) resolve();
            this.starredKey = this._getChainKey("markets");
            this.marketsKey = this._getChainKey("userMarkets");
            // Default markets setup
            let topMarkets = {
                markets_4018d784: [ // BTS MAIN NET
                    "OPEN.MKR", "BTS", "OPEN.ETH", "ICOO", "BTC", "OPEN.LISK", "BKT",
                    "OPEN.STEEM", "OPEN.GAME", "OCT", "USD", "CNY", "BTSR", "OBITS",
                    "OPEN.DGD", "EUR", "GOLD", "SILVER", "IOU.CNY", "OPEN.DASH",
                    "OPEN.USDT", "OPEN.EURT", "OPEN.BTC", "CADASTRAL", "BLOCKPAY", "BTWTY",
                    "OPEN.INCNT", "KAPITAL", "OPEN.MAID", "OPEN.SBD", "OPEN.GRC",
                    "YOYOW", "HERO", "RUBLE", "SMOKE", "STEALTH", "BRIDGE.BCO",
                    "BRIDGE.BTC", "KEXCOIN", "PPY", "OPEN.EOS", "OPEN.OMG", "CVCOIN",
                    "BRIDGE.ZNY", "BRIDGE.MONA", "OPEN.LTC", "GDEX.BTC"
                ],
                markets_39f5e2ed: [ // TESTNET
                    "PEG.FAKEUSD", "BTWTY"
                ]
            };

            let bases = {
                markets_4018d784: [ // BTS MAIN NET
                    "USD", "OPEN.BTC", "CNY", "BTS", "BTC"
                ],
                markets_39f5e2ed: [ // TESTNET
                    "TEST"
                ]
            };

            let coreAssets = {markets_4018d784: "BTS", markets_39f5e2ed: "TEST"};
            let coreAsset = coreAssets[this.starredKey] || "BTS";
            this.defaults.unit[0] = coreAsset;

            let chainBases = bases[this.starredKey] || bases.markets_4018d784;
            this.preferredBases = Immutable.List(chainBases);

            function addMarkets(target, base, markets) {
                markets.filter(a => {
                    return a !== base;
                }).forEach(market => {
                    target.push([`${market}_${base}`, {"quote": market,"base": base}]);
                });
            }

            let defaultMarkets = [];
            let chainMarkets = topMarkets[this.starredKey] || [];
            this.preferredBases.forEach(base => {
                addMarkets(defaultMarkets, base, chainMarkets);
            });

            this.defaultMarkets = Immutable.Map(defaultMarkets);
            this.starredMarkets = Immutable.Map(ss.get(this.starredKey, []));
            this.userMarkets = Immutable.Map(ss.get(this.marketsKey, {}));

            this.initDone = true;
            resolve();
        });
    }

    getSetting(setting) {
        return this.settings.get(setting);
    }

    onChangeSetting(payload) {
        this.settings = this.settings.set(
            payload.setting,
            payload.value
        );

        switch(payload.setting) {
            case "faucet_address":
                if (payload.value.indexOf("testnet") === -1) {
                    this.mainnet_faucet = payload.value;
                    ss.set("mainnet_faucet", payload.value);
                } else {
                    this.testnet_faucet = payload.value;
                    ss.set("testnet_faucet", payload.value);
                }
                break;

            case "apiServer":
                let faucetUrl = payload.value.indexOf("testnet") !== -1 ?
                    this.testnet_faucet : this.mainnet_faucet;
                this.settings = this.settings.set(
                    "faucet_address",
                    faucetUrl
                );
                break;

            case "walletLockTimeout":
                ss.set("lockTimeout", payload.value);
                break;

            default:
                break;
        }

        ss.set("settings_v3", this.settings.toJS());
    }

    onChangeViewSetting(payload) {
        for (let key in payload) {
            this.viewSettings = this.viewSettings.set(key, payload[key]);
        }

        ss.set("viewSettings_v1", this.viewSettings.toJS());
    }

    onChangeMarketDirection(payload) {
        for (let key in payload) {
            this.marketDirections = this.marketDirections.set(key, payload[key]);
        }
        ss.set("marketDirections", this.marketDirections.toJS());
    }

    onHideAsset(payload) {
        if (payload.id) {
            if (!payload.status) {
                this.hiddenAssets = this.hiddenAssets.delete(this.hiddenAssets.indexOf(payload.id));
            } else {
                this.hiddenAssets = this.hiddenAssets.push(payload.id);
            }
        }

        ss.set("hiddenAssets", this.hiddenAssets.toJS());
    }

    onAddStarMarket(market) {
        let marketID = market.quote + "_" + market.base;
        if (!this.starredMarkets.has(marketID)) {
            this.starredMarkets = this.starredMarkets.set(marketID, {quote: market.quote, base: market.base});

            ss.set(this.starredKey, this.starredMarkets.toJS());
        } else {
            return false;
        }
    }

    onSetUserMarket(payload) {
        let marketID = payload.quote + "_" + payload.base;
        if (payload.value) {
            this.userMarkets = this.userMarkets.set(marketID, {quote: payload.quote, base: payload.base});
        } else {
            this.userMarkets = this.userMarkets.delete(marketID);
        }
        ss.set(this.marketsKey, this.userMarkets.toJS());
    }

    onRemoveStarMarket(market) {
        let marketID = market.quote + "_" + market.base;

        this.starredMarkets = this.starredMarkets.delete(marketID);

        ss.set(this.starredKey, this.starredMarkets.toJS());
    }

    onClearStarredMarkets(){
        this.starredMarkets = Immutable.Map({});
        ss.set(this.starredKey, this.starredMarkets.toJS());
    }

    onAddWS(ws) {
        if (typeof ws === "string") {
            ws = {url: ws, location: null};
        }
        this.defaults.apiServer.push(ws);
        ss.set("defaults_v1", this.defaults);
    }

    onRemoveWS(index) {
        this.defaults.apiServer.splice(index, 1);
        ss.set("defaults_v1", this.defaults);
    }

    onClearSettings(resolve) {
        ss.remove("settings_v3");
        this.settings = this.defaultSettings;

        ss.set("settings_v3", this.settings.toJS());

        if (resolve) {
            resolve();
        }
    }

    onSwitchLocale({locale}) {
        this.onChangeSetting({setting: "locale", value: locale});
    }

    _getChainKey(key) {
        const chainId = Apis.instance().chain_id;
        return key + (chainId ? `_${chainId.substr(0, 8)}` : "");
    }

    onUpdateLatencies(latencies) {
        ss.set("apiLatencies", latencies);
        this.apiLatencies = latencies;
    }

    getLastBudgetObject() {
        return ss.get(this._getChainKey("lastBudgetObject"), "2.13.1");
    }

    setLastBudgetObject(value) {
        ss.set(this._getChainKey("lastBudgetObject"), value);
    }
}

export default alt.createStore(SettingsStore, "SettingsStore");
<|endoftext|>


<commit_before>var canvas, canvasContext;

window.onload = function() {
	canvas = document.getElementById('gameCanvas');
	canvasContext = canvas.getContext('2d');

    loadImages();
}

function imageLoadingDoneSoStartGame() {
	var framesPerSecond = 30;
	setInterval(updateAll, 1000/framesPerSecond);

setupInput();
carReset();

}

function updateAll() {
    moveAll();
    drawAll();
}

function moveAll() {
	 carMove();

	carTrackHandling();
}

// clear screen no longer needed (12.93)
/*
function clearScreen() {
	colorRect(0,0, canvas.width,canvas.height, 'black'); // clear screen
}
*/
function drawAll() {

    //clearScreen();
    drawTracks();
    carDraw();

}
<commit_msg>Finish of sect 13.100: loading screen/text message<commit_after>var canvas, canvasContext;

window.onload = function() {
	canvas = document.getElementById('gameCanvas');
    canvasContext = canvas.getContext('2d');

    colorRect(0,0, canvas.width,canvas.height, 'black'); // shows as loading, could display message/loading/whatever
    colorText("LOADING IMAGES...", canvas.width/2, canvas.height/2, 'white');

    loadImages();
}

function imageLoadingDoneSoStartGame() {
	var framesPerSecond = 30;
	setInterval(updateAll, 1000/framesPerSecond);

setupInput();
carReset();

}

function updateAll() {
    moveAll();
    drawAll();
}

function moveAll() {
	 carMove();

	carTrackHandling();
}

// clear screen no longer needed (12.93)
/*
function clearScreen() {
	colorRect(0,0, canvas.width,canvas.height, 'black'); // clear screen
}
*/
function drawAll() {

    //clearScreen();
    drawTracks();
    carDraw();

}
<|endoftext|>


<commit_before>//jquery 1.3.2 dependencies  : $.each, $.extend, $.ajax

// Uses CommonJS, AMD or browser globals to create a jQuery extension.
(function (factory) {
    if (typeof exports === 'object') {
        // Node/CommonJS
        factory(require('jquery'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function($) {

    var o = {};
    var dictionary = false; //not yet loaded
    var currentLang = false;
    var count_of_replacement = 0;

    function init(callback,options){
        $.extend(o,{
            //defaults
            interpolationPrefix : '__',
            interpolationSuffix : '__',
            pluralSuffix : '_plural',
            getSuffixMethod : function(count){ return ( count > 1 || typeof(count) == "string" )  ? o.pluralSuffix : ""; },
            maxRecursion : 50, //used while applying reuse of strings to avoid infinite loop
            reusePrefix : "$t(",
            reuseSuffix : ")",
            fallbackLang : 'en-US', // see Language fallback section
            dicoPath : 'locales', // see Dictionary section
            async : true, // might be used to force initialization, dictionary loading to be syncronous
            keyseparator : ".", // keys passed to $.jsperanttranslate use this separator
            setDollarT : true, // $.t aliases $.jsperanttranslate, nice shortcut
            dictionary : false, // to supply the dictionary instead of loading it using $.ajax. A (big) javascript object containing your namespaced translations
            lang : false, //specify a language to use
            suffixNotFound : ["suffix_not_found_", Math.random()].join('') // used internally by translate
        },options);
        if(!o.lang){o.lang = detectLanguage();}
        loadDictionary(o.lang,function(loadedLang){
            currentLang = loadedLang;
            if(o.setDollarT){$.t = $.t || translate;} //shortcut
            callback(translate);
        });
    }

    function applyReplacement(string,replacementHash){
        $.each(replacementHash,function(key,value){
            string = string.replace([o.interpolationPrefix,key,o.interpolationSuffix].join(''),value);
        });
        return string;
    }

    function applyReuse(translated,options){
        while (translated.indexOf(o.reusePrefix) != -1){
            count_of_replacement++;
            if(count_of_replacement > o.maxRecursion){break;} // safety net for too much recursion
            var index_of_opening = translated.indexOf(o.reusePrefix);
            var index_of_end_of_closing = translated.indexOf(o.reuseSuffix,index_of_opening) + o.reuseSuffix.length;
            var token = translated.substring(index_of_opening,index_of_end_of_closing);
            var token_sans_symbols = token.replace(o.reusePrefix,"").replace(o.reuseSuffix,"");
            var translated_token = _translate(token_sans_symbols,options);
            translated = translated.replace(token,translated_token);
        }
        return translated;
    }

    function detectLanguage(){
        if(navigator){
            return (navigator.language) ? navigator.language : navigator.userLanguage;
        }else{
            return o.fallbackLang;
        }
    }

    function containsCount(options){
       return (typeof options.count == 'number' || typeof options.count == 'string');
    }

    function getCountSuffix(options) {
       var suffix = o.getSuffixMethod(options.count);
       return ( typeof(suffix) == "string" ) ? suffix : '';
   }

    function translate(dottedkey,options){
        count_of_replacement = 0;
        return _translate(dottedkey,options);
    }

    /*
    options.defaultValue
    options.count
    */
    function _translate(dottedkey,options){
        options = options || {};
        var notfound = options.defaultValue || dottedkey;
        if(!dictionary){return notfound;} // No dictionary to translate from

        if(containsCount(options)){
            var optionsSansCount = $.extend({},options);
            delete optionsSansCount.count;
            optionsSansCount.defaultValue = o.suffixNotFound;
            var suffixKey = dottedkey + getCountSuffix(options);
            var translated = translate(suffixKey,optionsSansCount);
            if(translated != o.suffixNotFound){
                return applyReplacement(translated,{count:options.count});//apply replacement for count only
            }// else continue translation with original/singular key
        }

        var keys = dottedkey.split(o.keyseparator);
        var i = 0;
        var value = dictionary;
        while(keys[i]) {
            value = value && value[keys[i]];
            i++;
        }
        if(value){
            value = applyReplacement(value,options);
            value = applyReuse(value,options);
            return value;
        }else{
            return notfound;
        }
    }

    function loadDictionary(lang,doneCallback){
        if(o.dictionary){
            dictionary = o.dictionary;
            doneCallback(lang);
            return;
        }
        $.ajax({
            url: [o.dicoPath,"/", lang, '.json'].join(''),
            success: function(data,status,xhr){
                dictionary = data;
                doneCallback(lang);
            },
            error : function(xhr,status,error){
                if(lang != o.fallbackLang){
                    loadDictionary(o.fallbackLang,doneCallback);
                }else{
                    doneCallback(false);
                }
            },
            async : o.async,
            dataType: "json"
        });
    }

    function lang(){
        return currentLang;
    }

    $.jsperanto = $.jsperanto || {
        init:init,
        t:translate,
        translate:translate,
        detectLanguage : detectLanguage,
        lang : lang
    };
}));

<commit_msg>return promise<commit_after>//jquery 1.3.2 dependencies  : $.each, $.extend, $.ajax

// Uses CommonJS, AMD or browser globals to create a jQuery extension.
(function (factory) {
    if (typeof exports === 'object') {
        // Node/CommonJS
        factory(require('jquery'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function($) {

    var o = {};
    var dictionary = false; //not yet loaded
    var currentLang = false;
    var count_of_replacement = 0;

    function init(callback,options){
        $.extend(o,{
            //defaults
            interpolationPrefix : '__',
            interpolationSuffix : '__',
            pluralSuffix : '_plural',
            getSuffixMethod : function(count){ return ( count > 1 || typeof(count) == "string" )  ? o.pluralSuffix : ""; },
            maxRecursion : 50, //used while applying reuse of strings to avoid infinite loop
            reusePrefix : "$t(",
            reuseSuffix : ")",
            fallbackLang : 'en-US', // see Language fallback section
            dicoPath : 'locales', // see Dictionary section
            async : true, // might be used to force initialization, dictionary loading to be syncronous
            keyseparator : ".", // keys passed to $.jsperanttranslate use this separator
            setDollarT : true, // $.t aliases $.jsperanttranslate, nice shortcut
            dictionary : false, // to supply the dictionary instead of loading it using $.ajax. A (big) javascript object containing your namespaced translations
            lang : false, //specify a language to use
            suffixNotFound : ["suffix_not_found_", Math.random()].join('') // used internally by translate
        },options);
        if(!o.lang){o.lang = detectLanguage();}
        return loadDictionary(o.lang,function(loadedLang){
            currentLang = loadedLang;
            if(o.setDollarT){$.t = $.t || translate;} //shortcut
            if(callback) callback(translate);
        });
    }

    function applyReplacement(string,replacementHash){
        $.each(replacementHash,function(key,value){
            string = string.replace([o.interpolationPrefix,key,o.interpolationSuffix].join(''),value);
        });
        return string;
    }

    function applyReuse(translated,options){
        while (translated.indexOf(o.reusePrefix) != -1){
            count_of_replacement++;
            if(count_of_replacement > o.maxRecursion){break;} // safety net for too much recursion
            var index_of_opening = translated.indexOf(o.reusePrefix);
            var index_of_end_of_closing = translated.indexOf(o.reuseSuffix,index_of_opening) + o.reuseSuffix.length;
            var token = translated.substring(index_of_opening,index_of_end_of_closing);
            var token_sans_symbols = token.replace(o.reusePrefix,"").replace(o.reuseSuffix,"");
            var translated_token = _translate(token_sans_symbols,options);
            translated = translated.replace(token,translated_token);
        }
        return translated;
    }

    function detectLanguage(){
        if(navigator){
            return (navigator.language) ? navigator.language : navigator.userLanguage;
        }else{
            return o.fallbackLang;
        }
    }

    function containsCount(options){
       return (typeof options.count == 'number' || typeof options.count == 'string');
    }

    function getCountSuffix(options) {
       var suffix = o.getSuffixMethod(options.count);
       return ( typeof(suffix) == "string" ) ? suffix : '';
   }

    function translate(dottedkey,options){
        count_of_replacement = 0;
        return _translate(dottedkey,options);
    }

    /*
    options.defaultValue
    options.count
    */
    function _translate(dottedkey,options){
        options = options || {};
        var notfound = options.defaultValue || dottedkey;
        if(!dictionary){return notfound;} // No dictionary to translate from

        if(containsCount(options)){
            var optionsSansCount = $.extend({},options);
            delete optionsSansCount.count;
            optionsSansCount.defaultValue = o.suffixNotFound;
            var suffixKey = dottedkey + getCountSuffix(options);
            var translated = translate(suffixKey,optionsSansCount);
            if(translated != o.suffixNotFound){
                return applyReplacement(translated,{count:options.count});//apply replacement for count only
            }// else continue translation with original/singular key
        }

        var keys = dottedkey.split(o.keyseparator);
        var i = 0;
        var value = dictionary;
        while(keys[i]) {
            value = value && value[keys[i]];
            i++;
        }
        if(value){
            value = applyReplacement(value,options);
            value = applyReuse(value,options);
            return value;
        }else{
            return notfound;
        }
    }

    function loadDictionary(lang,doneCallback){
        if(o.dictionary){
            dictionary = o.dictionary;
            doneCallback(lang);
            return;
        }
        return $.ajax({
            url: [o.dicoPath,"/", lang, '.json'].join(''),
            success: function(data,status,xhr){
                dictionary = data;
                doneCallback(lang);
            },
            error : function(xhr,status,error){
                if(lang != o.fallbackLang){
                    loadDictionary(o.fallbackLang,doneCallback);
                }else{
                    doneCallback(false);
                }
            },
            async : o.async,
            dataType: "json"
        });
    }

    function lang(){
        return currentLang;
    }

    $.jsperanto = $.jsperanto || {
        init:init,
        t:translate,
        translate:translate,
        detectLanguage : detectLanguage,
        lang : lang
    };
}));
<|endoftext|>


<commit_before>import _ from 'lodash'
import { dbAdapter } from '../models'
import { extractMentions, extractMentionsWithIndices } from './mentions'

export const EVENT_TYPES = {
  MENTION_IN_POST:               'mention_in_post',
  MENTION_IN_COMMENT:            'mention_in_comment',
  MENTION_COMMENT_TO:            'mention_comment_to',
  USER_BANNED:                   'banned_user',
  USER_UNBANNED:                 'unbanned_user',
  BANNED_BY:                     'banned_by_user',
  UNBANNED_BY:                   'unbanned_by_user',
  USER_SUBSCRIBED:               'user_subscribed',
  USER_UNSUBSCRIBED:             'user_unsubscribed',
  SUBSCRIPTION_REQUESTED:        'subscription_requested',
  SUBSCRIPTION_REQUEST_REVOKED:  'subscription_request_revoked',
  SUBSCRIPTION_REQUEST_APPROVED: 'subscription_request_approved',
  SUBSCRIPTION_REQUEST_REJECTED: 'subscription_request_rejected',
  GROUP_CREATED:                 'group_created',
  GROUP_SUBSCRIBED:              'group_subscribed',
  GROUP_UNSUBSCRIBED:            'group_unsubscribed',
  GROUP_SUBSCRIPTION_REQUEST:    'group_subscription_requested',
  GROUP_REQUEST_REVOKED:         'group_subscription_request_revoked',
  GROUP_SUBSCRIPTION_APPROVED:   'group_subscription_approved',
  GROUP_SUBSCRIPTION_REJECTED:   'group_subscription_rejected',
  GROUP_ADMIN_PROMOTED:          'group_admin_promoted',
  GROUP_ADMIN_DEMOTED:           'group_admin_demoted',
  DIRECT_CREATED:                'direct',
  DIRECT_COMMENT_CREATED:        'direct_comment',

  MANAGED_GROUP_SUBSCRIPTION_APPROVED: 'managed_group_subscription_approved',
  MANAGED_GROUP_SUBSCRIPTION_REJECTED: 'managed_group_subscription_rejected',
};

export class EventService {
  static async onUserBanned(initiatorIntId, bannedUserIntId, wasSubscribed = false, hasRequestedSubscription = false) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_BANNED, initiatorIntId, bannedUserIntId);
    await dbAdapter.createEvent(bannedUserIntId, EVENT_TYPES.BANNED_BY, initiatorIntId, bannedUserIntId);
    if (wasSubscribed) {
      await this.onUserUnsubscribed(bannedUserIntId, initiatorIntId);
    }
    if (hasRequestedSubscription) {
      await this.onSubscriptionRequestRejected(bannedUserIntId, initiatorIntId);
    }
  }

  static async onUserUnbanned(initiatorIntId, unbannedUserIntId) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_UNBANNED, initiatorIntId, unbannedUserIntId);
    await dbAdapter.createEvent(unbannedUserIntId, EVENT_TYPES.UNBANNED_BY, initiatorIntId, unbannedUserIntId);
  }

  static async onUserSubscribed(initiatorIntId, subscribedUserIntId) {
    await dbAdapter.createEvent(subscribedUserIntId, EVENT_TYPES.USER_SUBSCRIBED, initiatorIntId, subscribedUserIntId);
  }

  static async onUserUnsubscribed(initiatorIntId, unsubscribedUserIntId) {
    await dbAdapter.createEvent(unsubscribedUserIntId, EVENT_TYPES.USER_UNSUBSCRIBED, initiatorIntId, unsubscribedUserIntId);
  }

  static async onSubscriptionRequestCreated(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUESTED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRevoked(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REVOKED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestApproved(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_APPROVED, toUserIntId, fromUserIntId);
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.USER_SUBSCRIBED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRejected(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REJECTED, toUserIntId, fromUserIntId);
  }

  static async onGroupCreated(ownerIntId, groupIntId) {
    await dbAdapter.createEvent(ownerIntId, EVENT_TYPES.GROUP_CREATED, ownerIntId, null, groupIntId);
  }

  static async onGroupSubscribed(initiatorIntId, subscribedGroup) {
    await this._notifyGroupAdmins(subscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIBED, initiatorIntId, null, subscribedGroup.intId);
    });
  }

  static async onGroupUnsubscribed(initiatorIntId, unsubscribedGroup) {
    await this._notifyGroupAdmins(unsubscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_UNSUBSCRIBED, initiatorIntId, null, unsubscribedGroup.intId);
    });
  }

  static async onGroupAdminPromoted(initiatorIntId, group, newAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === newAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_PROMOTED, initiatorIntId, newAdminIntId, group.intId);
    });
  }

  static async onGroupAdminDemoted(initiatorIntId, group, formerAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === formerAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_DEMOTED, initiatorIntId, formerAdminIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestCreated(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIPTION_REQUEST, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestRevoked(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_REQUEST_REVOKED, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestApproved(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
  }

  static async onGroupSubscriptionRequestRejected(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_REJECTED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_REJECTED, null, requesterIntId, group.intId);
  }

  static async onPostCreated(post, destinationFeedIds, author) {
    await this._processDirectMessagesForPost(post, destinationFeedIds, author);
    await this._processMentionsInPost(post, destinationFeedIds, author);
  }

  static async onCommentCreated(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor) {
    const postDestinationFeeds = await post.getPostedTo();
    await this._processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
    await this._processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
  }

  ////////////////////////////////////////////

  static async _processDirectMessagesForPost(post, destinationFeedIds, author) {
    const feeds = await dbAdapter.getTimelinesByIds(destinationFeedIds);
    const directFeeds = feeds.filter((f) => {
      return f.isDirects() && f.userId !== author.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      const directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);
      const promises = directReceivers.map((receiver) => {
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_CREATED, author.intId, receiver.intId, null, post.id, null, author.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInPost(post, destinationFeedIds, author) {
    const mentionedUsernames = _.uniq(extractMentions(post.body));

    if (mentionedUsernames.length === 0) {
      return;
    }

    let postGroupIntId = null;
    if (destinationFeedIds.length === 1) {
      const postFeed = await dbAdapter.getTimelineById(destinationFeedIds[0]);
      const feedOwner = await postFeed.getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const usersBannedByPostAuthor = await author.getBanIds();
    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentionedUsernames);
    const promises = mentionedUsers.map(async (user) => {
      if (!user || user.type !== 'user') {
        return null;
      }

      if (author.id === user.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(user.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await user.getBanIds();
      if (usersBannedByCurrentUser.includes(author.id)) {
        return null;
      }

      const isVisible = await post.canShow(user.id);
      if (!isVisible) {
        return null;
      }

      return dbAdapter.createEvent(user.intId, EVENT_TYPES.MENTION_IN_POST, author.intId, user.intId, postGroupIntId, post.id, null, author.intId);
    });
    await Promise.all(promises);
  }

  static async _processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    const feeds = postDestinationFeeds;
    const directFeeds = feeds.filter((f) => {
      return f.isDirects() && f.userId !== commentAuthor.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      let directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);

      directReceivers = directReceivers.filter((r) => {
        return !usersBannedByCommentAuthor.includes(r.id);
      });

      const promises = directReceivers.map(async (receiver) => {
        let usersBannedByReceiver = [];
        if (receiver.id === commentAuthor.id) {
          usersBannedByReceiver = usersBannedByCommentAuthor;
        } else {
          if (receiver.id === postAuthor.id) {
            usersBannedByReceiver = usersBannedByPostAuthor;
          } else {
            usersBannedByReceiver = await receiver.getBanIds();
          }
        }

        if (usersBannedByReceiver.includes(commentAuthor.id)) {
          return null;
        }
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_COMMENT_CREATED, commentAuthor.intId, receiver.intId, null, post.id, comment.id, postAuthor.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    let mentions = extractMentionsWithIndices(comment.body);

    if (mentions.length == 0) {
      return;
    }

    let  postGroupIntId = null;
    const feeds = postDestinationFeeds;
    if (feeds.length === 1) {
      const feedOwner = await feeds[0].getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const postDestinationsFeedsOwners = feeds.map((f) => f.userId);
    const nonDirectFeeds = feeds.filter((f) => !f.isDirects());
    const nonDirectFeedsIds = nonDirectFeeds.map((f) => f.id);
    const nonDirectFeedsOwnerIds = nonDirectFeeds.map((f) => f.userId);
    const postIsPublic = await dbAdapter.someUsersArePublic(nonDirectFeedsOwnerIds, false);

    const replyToUser = mentions.find((m) => { return m.indices[0] === 0; });

    if (replyToUser) {
      _.remove(mentions, (m) => { return m.username == replyToUser.username && m.indices[0] != 0; });
    }
    mentions = _.uniqBy(mentions, 'username');

    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentions.map((m) => m.username));
    let usersSubscriptionsStatus = [];
    if (!postIsPublic) {
      usersSubscriptionsStatus = await dbAdapter.areUsersSubscribedToOneOfTimelines(mentionedUsers.map((u) => u.id), nonDirectFeedsIds);
    }
    const promises = mentions.map(async (m) => {
      const mentionedUser = mentionedUsers.find((u) => u.username === m.username);
      if (!mentionedUser || mentionedUser.type !== 'user') {
        return null;
      }

      if (commentAuthor.id === mentionedUser.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(mentionedUser.id) || usersBannedByCommentAuthor.includes(mentionedUser.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await mentionedUser.getBanIds();
      if (usersBannedByCurrentUser.includes(commentAuthor.id) || usersBannedByCurrentUser.includes(postAuthor.id)) {
        return null;
      }

      if (!postDestinationsFeedsOwners.includes(mentionedUser.id)) {
        if (nonDirectFeeds.length === 0) {
          return null;
        }

        if (!postIsPublic) {
          const subscriptionStatus = usersSubscriptionsStatus.find((u) => u.uid === mentionedUser.id);
          if (!subscriptionStatus.is_subscribed) {
            return null;
          }
        }
      }

      if (m.indices[0] === 0) {
        return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_COMMENT_TO, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
      }
      return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_IN_COMMENT, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
    });
    await Promise.all(promises);
  }

  static async _notifyGroupAdmins(group, adminNotifier) {
    const groupAdminsIds = await dbAdapter.getGroupAdministratorsIds(group.id);
    const admins = await dbAdapter.getUsersByIds(groupAdminsIds);

    const promises = admins.map((adminUser) => {
      return adminNotifier(adminUser);
    });
    await Promise.all(promises);
  }
}
<commit_msg>Reuse post destination feeds data in EventService.onPostCreated().<commit_after>import _ from 'lodash'
import { dbAdapter } from '../models'
import { extractMentions, extractMentionsWithIndices } from './mentions'

export const EVENT_TYPES = {
  MENTION_IN_POST:               'mention_in_post',
  MENTION_IN_COMMENT:            'mention_in_comment',
  MENTION_COMMENT_TO:            'mention_comment_to',
  USER_BANNED:                   'banned_user',
  USER_UNBANNED:                 'unbanned_user',
  BANNED_BY:                     'banned_by_user',
  UNBANNED_BY:                   'unbanned_by_user',
  USER_SUBSCRIBED:               'user_subscribed',
  USER_UNSUBSCRIBED:             'user_unsubscribed',
  SUBSCRIPTION_REQUESTED:        'subscription_requested',
  SUBSCRIPTION_REQUEST_REVOKED:  'subscription_request_revoked',
  SUBSCRIPTION_REQUEST_APPROVED: 'subscription_request_approved',
  SUBSCRIPTION_REQUEST_REJECTED: 'subscription_request_rejected',
  GROUP_CREATED:                 'group_created',
  GROUP_SUBSCRIBED:              'group_subscribed',
  GROUP_UNSUBSCRIBED:            'group_unsubscribed',
  GROUP_SUBSCRIPTION_REQUEST:    'group_subscription_requested',
  GROUP_REQUEST_REVOKED:         'group_subscription_request_revoked',
  GROUP_SUBSCRIPTION_APPROVED:   'group_subscription_approved',
  GROUP_SUBSCRIPTION_REJECTED:   'group_subscription_rejected',
  GROUP_ADMIN_PROMOTED:          'group_admin_promoted',
  GROUP_ADMIN_DEMOTED:           'group_admin_demoted',
  DIRECT_CREATED:                'direct',
  DIRECT_COMMENT_CREATED:        'direct_comment',

  MANAGED_GROUP_SUBSCRIPTION_APPROVED: 'managed_group_subscription_approved',
  MANAGED_GROUP_SUBSCRIPTION_REJECTED: 'managed_group_subscription_rejected',
};

export class EventService {
  static async onUserBanned(initiatorIntId, bannedUserIntId, wasSubscribed = false, hasRequestedSubscription = false) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_BANNED, initiatorIntId, bannedUserIntId);
    await dbAdapter.createEvent(bannedUserIntId, EVENT_TYPES.BANNED_BY, initiatorIntId, bannedUserIntId);
    if (wasSubscribed) {
      await this.onUserUnsubscribed(bannedUserIntId, initiatorIntId);
    }
    if (hasRequestedSubscription) {
      await this.onSubscriptionRequestRejected(bannedUserIntId, initiatorIntId);
    }
  }

  static async onUserUnbanned(initiatorIntId, unbannedUserIntId) {
    await dbAdapter.createEvent(initiatorIntId, EVENT_TYPES.USER_UNBANNED, initiatorIntId, unbannedUserIntId);
    await dbAdapter.createEvent(unbannedUserIntId, EVENT_TYPES.UNBANNED_BY, initiatorIntId, unbannedUserIntId);
  }

  static async onUserSubscribed(initiatorIntId, subscribedUserIntId) {
    await dbAdapter.createEvent(subscribedUserIntId, EVENT_TYPES.USER_SUBSCRIBED, initiatorIntId, subscribedUserIntId);
  }

  static async onUserUnsubscribed(initiatorIntId, unsubscribedUserIntId) {
    await dbAdapter.createEvent(unsubscribedUserIntId, EVENT_TYPES.USER_UNSUBSCRIBED, initiatorIntId, unsubscribedUserIntId);
  }

  static async onSubscriptionRequestCreated(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUESTED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRevoked(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REVOKED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestApproved(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_APPROVED, toUserIntId, fromUserIntId);
    await dbAdapter.createEvent(toUserIntId, EVENT_TYPES.USER_SUBSCRIBED, fromUserIntId, toUserIntId);
  }

  static async onSubscriptionRequestRejected(fromUserIntId, toUserIntId) {
    await dbAdapter.createEvent(fromUserIntId, EVENT_TYPES.SUBSCRIPTION_REQUEST_REJECTED, toUserIntId, fromUserIntId);
  }

  static async onGroupCreated(ownerIntId, groupIntId) {
    await dbAdapter.createEvent(ownerIntId, EVENT_TYPES.GROUP_CREATED, ownerIntId, null, groupIntId);
  }

  static async onGroupSubscribed(initiatorIntId, subscribedGroup) {
    await this._notifyGroupAdmins(subscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIBED, initiatorIntId, null, subscribedGroup.intId);
    });
  }

  static async onGroupUnsubscribed(initiatorIntId, unsubscribedGroup) {
    await this._notifyGroupAdmins(unsubscribedGroup, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_UNSUBSCRIBED, initiatorIntId, null, unsubscribedGroup.intId);
    });
  }

  static async onGroupAdminPromoted(initiatorIntId, group, newAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === newAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_PROMOTED, initiatorIntId, newAdminIntId, group.intId);
    });
  }

  static async onGroupAdminDemoted(initiatorIntId, group, formerAdminIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      if (adminUser.intId === formerAdminIntId) {
        return null;
      }
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_ADMIN_DEMOTED, initiatorIntId, formerAdminIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestCreated(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_SUBSCRIPTION_REQUEST, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestRevoked(initiatorIntId, group) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.GROUP_REQUEST_REVOKED, initiatorIntId, initiatorIntId, group.intId);
    });
  }

  static async onGroupSubscriptionRequestApproved(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_APPROVED, adminIntId, requesterIntId, group.intId);
  }

  static async onGroupSubscriptionRequestRejected(adminIntId, group, requesterIntId) {
    await this._notifyGroupAdmins(group, (adminUser) => {
      return dbAdapter.createEvent(adminUser.intId, EVENT_TYPES.MANAGED_GROUP_SUBSCRIPTION_REJECTED, adminIntId, requesterIntId, group.intId);
    });
    await dbAdapter.createEvent(requesterIntId, EVENT_TYPES.GROUP_SUBSCRIPTION_REJECTED, null, requesterIntId, group.intId);
  }

  static async onPostCreated(post, destinationFeedIds, author) {
    const destinationFeeds = await dbAdapter.getTimelinesByIds(destinationFeedIds);
    await this._processDirectMessagesForPost(post, destinationFeeds, author);
    await this._processMentionsInPost(post, destinationFeeds, author);
  }

  static async onCommentCreated(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor) {
    const postDestinationFeeds = await post.getPostedTo();
    await this._processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
    await this._processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds);
  }

  ////////////////////////////////////////////

  static async _processDirectMessagesForPost(post, destinationFeeds, author) {
    const directFeeds = destinationFeeds.filter((f) => {
      return f.isDirects() && f.userId !== author.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      const directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);
      const promises = directReceivers.map((receiver) => {
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_CREATED, author.intId, receiver.intId, null, post.id, null, author.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInPost(post, destinationFeeds, author) {
    const mentionedUsernames = _.uniq(extractMentions(post.body));

    if (mentionedUsernames.length === 0) {
      return;
    }

    let postGroupIntId = null;
    if (destinationFeeds.length === 1) {
      const postFeed = destinationFeeds[0];
      const feedOwner = await postFeed.getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const usersBannedByPostAuthor = await author.getBanIds();
    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentionedUsernames);
    const promises = mentionedUsers.map(async (user) => {
      if (!user || user.type !== 'user') {
        return null;
      }

      if (author.id === user.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(user.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await user.getBanIds();
      if (usersBannedByCurrentUser.includes(author.id)) {
        return null;
      }

      const isVisible = await post.canShow(user.id);
      if (!isVisible) {
        return null;
      }

      return dbAdapter.createEvent(user.intId, EVENT_TYPES.MENTION_IN_POST, author.intId, user.intId, postGroupIntId, post.id, null, author.intId);
    });
    await Promise.all(promises);
  }

  static async _processDirectMessagesForComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    const feeds = postDestinationFeeds;
    const directFeeds = feeds.filter((f) => {
      return f.isDirects() && f.userId !== commentAuthor.id;
    });

    if (directFeeds.length > 0) {
      const directReceiversIds = directFeeds.map((f) => {
        return f.userId;
      });

      let directReceivers = await dbAdapter.getUsersByIds(directReceiversIds);

      directReceivers = directReceivers.filter((r) => {
        return !usersBannedByCommentAuthor.includes(r.id);
      });

      const promises = directReceivers.map(async (receiver) => {
        let usersBannedByReceiver = [];
        if (receiver.id === commentAuthor.id) {
          usersBannedByReceiver = usersBannedByCommentAuthor;
        } else {
          if (receiver.id === postAuthor.id) {
            usersBannedByReceiver = usersBannedByPostAuthor;
          } else {
            usersBannedByReceiver = await receiver.getBanIds();
          }
        }

        if (usersBannedByReceiver.includes(commentAuthor.id)) {
          return null;
        }
        return dbAdapter.createEvent(receiver.intId, EVENT_TYPES.DIRECT_COMMENT_CREATED, commentAuthor.intId, receiver.intId, null, post.id, comment.id, postAuthor.intId);
      });
      await Promise.all(promises);
    }
  }

  static async _processMentionsInComment(comment, post, commentAuthor, postAuthor, usersBannedByPostAuthor, usersBannedByCommentAuthor, postDestinationFeeds) {
    let mentions = extractMentionsWithIndices(comment.body);

    if (mentions.length == 0) {
      return;
    }

    let  postGroupIntId = null;
    const feeds = postDestinationFeeds;
    if (feeds.length === 1) {
      const feedOwner = await feeds[0].getUser();
      if (feedOwner.type === 'group') {
        postGroupIntId = feedOwner.intId;
      }
    }

    const postDestinationsFeedsOwners = feeds.map((f) => f.userId);
    const nonDirectFeeds = feeds.filter((f) => !f.isDirects());
    const nonDirectFeedsIds = nonDirectFeeds.map((f) => f.id);
    const nonDirectFeedsOwnerIds = nonDirectFeeds.map((f) => f.userId);
    const postIsPublic = await dbAdapter.someUsersArePublic(nonDirectFeedsOwnerIds, false);

    const replyToUser = mentions.find((m) => { return m.indices[0] === 0; });

    if (replyToUser) {
      _.remove(mentions, (m) => { return m.username == replyToUser.username && m.indices[0] != 0; });
    }
    mentions = _.uniqBy(mentions, 'username');

    const mentionedUsers = await dbAdapter.getFeedOwnersByUsernames(mentions.map((m) => m.username));
    let usersSubscriptionsStatus = [];
    if (!postIsPublic) {
      usersSubscriptionsStatus = await dbAdapter.areUsersSubscribedToOneOfTimelines(mentionedUsers.map((u) => u.id), nonDirectFeedsIds);
    }
    const promises = mentions.map(async (m) => {
      const mentionedUser = mentionedUsers.find((u) => u.username === m.username);
      if (!mentionedUser || mentionedUser.type !== 'user') {
        return null;
      }

      if (commentAuthor.id === mentionedUser.id) {
        return null;
      }

      if (usersBannedByPostAuthor.includes(mentionedUser.id) || usersBannedByCommentAuthor.includes(mentionedUser.id)) {
        return null;
      }

      const usersBannedByCurrentUser = await mentionedUser.getBanIds();
      if (usersBannedByCurrentUser.includes(commentAuthor.id) || usersBannedByCurrentUser.includes(postAuthor.id)) {
        return null;
      }

      if (!postDestinationsFeedsOwners.includes(mentionedUser.id)) {
        if (nonDirectFeeds.length === 0) {
          return null;
        }

        if (!postIsPublic) {
          const subscriptionStatus = usersSubscriptionsStatus.find((u) => u.uid === mentionedUser.id);
          if (!subscriptionStatus.is_subscribed) {
            return null;
          }
        }
      }

      if (m.indices[0] === 0) {
        return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_COMMENT_TO, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
      }
      return dbAdapter.createEvent(mentionedUser.intId, EVENT_TYPES.MENTION_IN_COMMENT, commentAuthor.intId, mentionedUser.intId, postGroupIntId, post.id, comment.id, postAuthor.intId);
    });
    await Promise.all(promises);
  }

  static async _notifyGroupAdmins(group, adminNotifier) {
    const groupAdminsIds = await dbAdapter.getGroupAdministratorsIds(group.id);
    const admins = await dbAdapter.getUsersByIds(groupAdminsIds);

    const promises = admins.map((adminUser) => {
      return adminNotifier(adminUser);
    });
    await Promise.all(promises);
  }
}
<|endoftext|>


<commit_before>var moment = require('moment');
var schedule = require('node-schedule');

module.exports = function (config, controller, model) {
  // give the bot something to listen for.
  controller.hears('hello',['direct_message','direct_mention','mention'], function (bot, message) {
    bot.reply(message,'Hello yourself.');
  });

  controller.hears('kebab',['direct_message','direct_mention','mention'], function (bot, message) {
    bot.reply(message,'Sorry, you should speak to Martin about kebab...');
  });

  controller.hears('sha1easteregg',['direct_message','direct_mention','mention'], function (bot, message) {
    controller.sayToChannel(config.get('announceChannel'), 'Holy shit a collision in SHA-1 :scream:');
  });

  controller.hears('jag heter (\\S+)', ['direct_message', 'direct_mention', 'mention'], function (bot, message) {
    bot.reply(message, 'Hej ' + message.match[1] + ', du är söt! Puss på dig! :kissing_heart:');
    console.log(message);
  });

  var errorWrap = function (bot, message, cb) {
    return function (err, data) {
      if (err) {
        bot.reply(message, 'Oopps. Something went wrong. Sorry :(');
        console.log(err);
      } else {
        cb(bot, message, data);
      }
    };
  };

  // Blacklist management
  // TODO: instead of regex date, check actual date validity?
  controller.hears('^blacklist (\\d{4}-[01]\\d-[0-3]\\d)$', ['direct_message', 'direct_mention'], function (bot, message)  {
    model.blacklist.add(message.match[1], errorWrap(bot, message, function (bot, message) {
      bot.reply(message, 'I have added ' + message.match[1] + ' to the blacklist!');
    }));
  });

  controller.hears('^whitelist (\\d{4}-[01]\\d-[0-3]\\d)$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.blacklist.remove(message.match[1], errorWrap(bot, message, function (bot, message) {
      bot.reply(message, 'I have removed ' + message.match[1] + ' from the blacklist!');
    }));
  });

  controller.hears('^blacklist$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.blacklist.list(errorWrap(bot, message, function (bot, message, data) {
      let msg = 'This is the current blacklist:\n';
      data.forEach(date => { msg += date + '\n'; });
      bot.reply(message, msg);
    }));
  });

  // Member management
  controller.hears('^add <@(.*)>$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.add(message.match[1], errorWrap(bot, message, function (bot, message, data) {
      bot.reply(message, 'I have added ' + data.name + ' (<@' + data.id + '>, ' + data.email + ') to the fika list!');
    }));
  });

  controller.hears('^remove <@(.*)>$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.remove(message.match[1], errorWrap(bot, message, function (bot, message, data) {
      bot.reply(message, 'I have removed ' + data.name + ' (<@' + data.id + '>, ' + data.email + ') from the fika list!');
    }));
  });

  controller.hears('^rotate$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.rotate(errorWrap(bot, message, function (bot, message) {
      bot.reply(message, 'I have rotated the fika queue one step.');
    }));
  });

  controller.hears('^move <@(.*)> (\\d+)$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.move(message.match[1], message.match[2], errorWrap(bot, message, function (bot, message, data) {
      bot.reply(message, 'I have moved ' + data.name + ' (<@' + data.id + '>) to position ' + message.match[2] + ' in the fika order.');
    }));
  });

  controller.hears('^list$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.list(errorWrap(bot, message, function (bot, message, data) {
      let msg = 'This is the current member list, in next-fika order:\n';
      data.forEach(member => { msg += member.name + ' (' + member.username + ')\n'; });
      bot.reply(message, msg);
    }));
  });

  // Sometimes we just want to ask when the next fika is.
  controller.hears(['next', 'nästa', 'fika'], ['direct_message', 'direct_mention'], function (bot, message) {
    model.nextFika(function (err, data) {
      if (!err) {
        bot.reply(message, 'The next fika takes place on ' + data.date.format('YYYY-MM-DD') + ' and is served by ' + data.member.name);
      } else {
        bot.reply(message, 'Something went wrong. Sorry :(')
        console.log('Failed to print next fika:', err);
      }
    });
  });

  // We want a periodic fika remainder at certain times.
  // The "every sunday reminder" for next week's fika.
  var weekReminder = schedule.scheduleJob(config.get('weeklyReminder'), function () {
    console.log('SCHEDULE: Fika later this week job launching.');
    model.nextFika(function (err, data) {
      if (err) { console.log('Failed to fetch next fika info:', err); }
      if (data.date.subtract(7, 'days') < moment()) { // If fika day is within 7 seven days from today.
        controller.sayToChannel(config.get('announceChannel'),
          'This is a gentle reminder that fika will be provided by ' + data.member.name +
          ' this coming ' + data.date.format('dddd') + '.\n\nBest Wishes,\nFARFAR');
        controller.sayToMember(data.member, 'This is a gentle reminder that *you* are scheduled ' +
          'to provide fika this coming ' + data.date.format('dddd') + '.\n\nBest Wishes,\nFARFAR',
          'You serve fika this coming ' + data.date.format('dddd'));
      } else {
        console.log('SCHEDULE: No fika this week, >= 7 days until next occasion.');
      }
    });
  });

  // The "every <fikaday> reminder" for the same day's fika.
  var sameDayReminder = schedule.scheduleJob(config.get('sameDayReminder'), function () {
    // First we must check that today is indeed the fika day (this day could have been blacklisted!)
    console.log('SCHEDULE: Fika later today job launching.');
    model.nextFika(function (err, data) {
      if (err) { console.log('Failed to fetch next fika info:', err); }
      if (data.date.format('YYYY-MM-DD') == moment().format('YYYY-MM-DD')) {
        controller.sayToChannel(config.get('announceChannel'),
          'This is a gentle reminder that fika will be provided by ' + data.member.name + ' at 15:00 today.\n\nBest Wishes,\nFARFAR');
        controller.sayToMember(data.member, 'This is a gentle reminder that *you* are scheduled ' +
          'to provide fika at 15:00 today.\n\nBest Wishes,\nFARFAR', 'You serve fika today');
      } else {
        console.log('SCHEDULE: No fika today. Next fika day is not today.');
      }
    });
  });

  // The "IT IS FUCKING FIKA RIGHT NOW reminder" when fika starts in 3 minutes.
  // Note that this also rotatis the fika schedule.
  var sameDayReminder = schedule.scheduleJob(config.get('rightNowReminder'), function () {
    console.log('SCHEDULE: Fika right now job launching.');
    model.nextFika(function (err, data) {
      if (err) { console.log('Failed to fetch next fika info:', err); }
      if (data.date.format('YYYY-MM-DD') == moment().format('YYYY-MM-DD')) {
        controller.sayToChannel(config.get('announceChannel'),
          'This is the final call: Fika begins pretty much now!\n\nBest Wishes,\nFARFAR');
        model.members.rotate(function (err) {
          if (err) {
            console.log('Failed to rotate the fika queue!');
          }
        });
      } else {
        console.log('SCHEDULE: No fika today. Next fika day is not today.', data.date.format('YYYY-MM-DD'));
      }
   });
  });
};
<commit_msg>Add help response.<commit_after>var moment = require('moment');
var schedule = require('node-schedule');

module.exports = function (config, controller, model) {
  // give the bot something to listen for.
  controller.hears('hello',['direct_message','direct_mention','mention'], function (bot, message) {
    bot.reply(message,'Hello yourself.');
  });

  controller.hears('kebab',['direct_message','direct_mention','mention'], function (bot, message) {
    bot.reply(message,'Sorry, you should speak to Martin about kebab.');
  });

  controller.hears('sha1easteregg',['direct_message','direct_mention','mention'], function (bot, message) {
    controller.sayToChannel(config.get('announceChannel'), 'Holy shit a collision in SHA-1 :scream:');
  });

  controller.hears('^help$', ['direct_message', 'direct_mention'], function (bot, message) {
    var helpText = `:man: *FARFAR - _Fully Automated Robotic Fika Announcer and Reminder_*
:cookie: Fika-related commands
> • \`fika/next/nästa\`: mention any of these words, and FARFAR will respond with the next fika date, and the person responsible.
:busts_in_silhouette: Member management commands
> • \`add @user\`: add new member to fika group
> • \`remove @user\`: remove someone from fika group
> • \`list\`: list all members of the fika group in next-fika order
> • \`rotate\`: move this week's fika responsible to the last position in the fika queue.
> • \`move @user 2\`: move user to given position (2 in this case) of the fika queue. zero-indexed.
:date: Date blacklisting commands
> • \`blacklist 2017-03-19\`: add date to blacklist, no fika will be served that day.
> • \`whitelist 2017-03-19\`: remove date from blacklist.
> • \`blacklist\`: list all dates in the blacklist.`;
    bot.reply(message, helpText);
  });

  controller.hears('jag heter (\\S+)', ['direct_message', 'direct_mention', 'mention'], function (bot, message) {
    bot.reply(message, 'Hej ' + message.match[1] + ', du är söt! Puss på dig! :kissing_heart:');
    console.log(message);
  });

  var errorWrap = function (bot, message, cb) {
    return function (err, data) {
      if (err) {
        bot.reply(message, 'Oopps. Something went wrong. Sorry :(');
        console.log(err);
      } else {
        cb(bot, message, data);
      }
    };
  };

  // Blacklist management
  // TODO: instead of regex date, check actual date validity?
  controller.hears('^blacklist (\\d{4}-[01]\\d-[0-3]\\d)$', ['direct_message', 'direct_mention'], function (bot, message)  {
    model.blacklist.add(message.match[1], errorWrap(bot, message, function (bot, message) {
      bot.reply(message, 'I have added ' + message.match[1] + ' to the blacklist!');
    }));
  });

  controller.hears('^whitelist (\\d{4}-[01]\\d-[0-3]\\d)$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.blacklist.remove(message.match[1], errorWrap(bot, message, function (bot, message) {
      bot.reply(message, 'I have removed ' + message.match[1] + ' from the blacklist!');
    }));
  });

  controller.hears('^blacklist$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.blacklist.list(errorWrap(bot, message, function (bot, message, data) {
      let msg = 'This is the current blacklist:\n';
      data.forEach(date => { msg += date + '\n'; });
      bot.reply(message, msg);
    }));
  });

  // Member management
  controller.hears('^add <@(.*)>$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.add(message.match[1], errorWrap(bot, message, function (bot, message, data) {
      bot.reply(message, 'I have added ' + data.name + ' (<@' + data.id + '>, ' + data.email + ') to the fika list!');
    }));
  });

  controller.hears('^remove <@(.*)>$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.remove(message.match[1], errorWrap(bot, message, function (bot, message, data) {
      bot.reply(message, 'I have removed ' + data.name + ' (<@' + data.id + '>, ' + data.email + ') from the fika list!');
    }));
  });

  controller.hears('^rotate$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.rotate(errorWrap(bot, message, function (bot, message) {
      bot.reply(message, 'I have rotated the fika queue one step.');
    }));
  });

  controller.hears('^move <@(.*)> (\\d+)$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.move(message.match[1], message.match[2], errorWrap(bot, message, function (bot, message, data) {
      bot.reply(message, 'I have moved ' + data.name + ' (<@' + data.id + '>) to position ' + message.match[2] + ' in the fika order.');
    }));
  });

  controller.hears('^list$', ['direct_message', 'direct_mention'], function (bot, message) {
    model.members.list(errorWrap(bot, message, function (bot, message, data) {
      let msg = 'This is the current member list, in next-fika order:\n';
      data.forEach(member => { msg += member.name + ' (' + member.username + ')\n'; });
      bot.reply(message, msg);
    }));
  });

  // Sometimes we just want to ask when the next fika is.
  controller.hears(['next', 'nästa', 'fika'], ['direct_message', 'direct_mention'], function (bot, message) {
    model.nextFika(function (err, data) {
      if (!err) {
        bot.reply(message, 'The next fika takes place on ' + data.date.format('YYYY-MM-DD') + ' and is served by ' + data.member.name);
      } else {
        bot.reply(message, 'Something went wrong. Sorry :(')
        console.log('Failed to print next fika:', err);
      }
    });
  });

  // We want a periodic fika remainder at certain times.
  // The "every sunday reminder" for next week's fika.
  var weekReminder = schedule.scheduleJob(config.get('weeklyReminder'), function () {
    console.log('SCHEDULE: Fika later this week job launching.');
    model.nextFika(function (err, data) {
      if (err) { console.log('Failed to fetch next fika info:', err); }
      if (data.date.subtract(7, 'days') < moment()) { // If fika day is within 7 seven days from today.
        controller.sayToChannel(config.get('announceChannel'),
          'This is a gentle reminder that fika will be provided by ' + data.member.name +
          ' this coming ' + data.date.format('dddd') + '.\n\nBest Wishes,\nFARFAR');
        controller.sayToMember(data.member, 'This is a gentle reminder that *you* are scheduled ' +
          'to provide fika this coming ' + data.date.format('dddd') + '.\n\nBest Wishes,\nFARFAR',
          'You serve fika this coming ' + data.date.format('dddd'));
      } else {
        console.log('SCHEDULE: No fika this week, >= 7 days until next occasion.');
      }
    });
  });

  // The "every <fikaday> reminder" for the same day's fika.
  var sameDayReminder = schedule.scheduleJob(config.get('sameDayReminder'), function () {
    // First we must check that today is indeed the fika day (this day could have been blacklisted!)
    console.log('SCHEDULE: Fika later today job launching.');
    model.nextFika(function (err, data) {
      if (err) { console.log('Failed to fetch next fika info:', err); }
      if (data.date.format('YYYY-MM-DD') == moment().format('YYYY-MM-DD')) {
        controller.sayToChannel(config.get('announceChannel'),
          'This is a gentle reminder that fika will be provided by ' + data.member.name + ' at 15:00 today.\n\nBest Wishes,\nFARFAR');
        controller.sayToMember(data.member, 'This is a gentle reminder that *you* are scheduled ' +
          'to provide fika at 15:00 today.\n\nBest Wishes,\nFARFAR', 'You serve fika today');
      } else {
        console.log('SCHEDULE: No fika today. Next fika day is not today.');
      }
    });
  });

  // The "IT IS FUCKING FIKA RIGHT NOW reminder" when fika starts in 3 minutes.
  // Note that this also rotatis the fika schedule.
  var sameDayReminder = schedule.scheduleJob(config.get('rightNowReminder'), function () {
    console.log('SCHEDULE: Fika right now job launching.');
    model.nextFika(function (err, data) {
      if (err) { console.log('Failed to fetch next fika info:', err); }
      if (data.date.format('YYYY-MM-DD') == moment().format('YYYY-MM-DD')) {
        controller.sayToChannel(config.get('announceChannel'),
          'This is the final call: Fika begins pretty much now!\n\nBest Wishes,\nFARFAR');
        model.members.rotate(function (err) {
          if (err) {
            console.log('Failed to rotate the fika queue!');
          }
        });
      } else {
        console.log('SCHEDULE: No fika today. Next fika day is not today.', data.date.format('YYYY-MM-DD'));
      }
   });
  });
};
<|endoftext|>


<commit_before>(function() {
  var $createKeyframeStyleTag, animationPlayState, playStateRunning, vendorPrefix;

  $createKeyframeStyleTag = function(params) {
    return $("<style>").attr({
      "class": "keyframe-style",
      id: params.id,
      type: "text/css"
    }).appendTo("head");
  };

  $.keyframe = {
    vendorPrefix: function() {
      var ua;
      ua = navigator.userAgent;
      if (ua.indexOf("Opera") !== -1) {
        return "-o-";
      } else if (ua.indexOf("MSIE") !== -1) {
        return "-ms-";
      } else if (ua.indexOf("WebKit") !== -1) {
        return "-webkit-";
      } else {
        return "";
      }
    },
    isSupported: function() {
      var animationSupport, element, pfx;
      element = $('body').get(0);
      animationSupport = false;
      if (element.style.animationName) {
        animationSupport = true;
      } else {
        pfx = this.vendorPrefix().slice(1, -1);
        if (element.style[pfx + "AnimationName"]) {
          animationSupport = true;
        }
      }
      return animationSupport;
    },
    generate: function(frameData) {
      var $elems, $frameStyle, css, frameName, property;
      frameName = frameData.name || "";
      css = "@" + (this.vendorPrefix()) + "keyframes " + frameName + " {";
      for (property in frameData) {
        if (property !== "name") {
          css += "" + property + "{" + frameData[property] + "}";
        }
      }
      css += "}";
      $frameStyle = $("style#" + frameData.name);
      if ($frameStyle.length > 0) {
        $frameStyle.html(css);
        $elems = $("*").filter(function() {
          return this.style["" + ($.keyframe.vendorPrefix().slice(1, -1)) + "AnimationName"] === frameName;
        });
        return $elems.each(function() {
          var $el, options;
          $el = $(this);
          options = $el.data("keyframeOptions");
          return $el.resetKeyframe(function() {
            return $el.playKeyframe(options);
          });
        });
      } else {
        return $createKeyframeStyleTag({
          id: frameName
        }).append(css);
      }
    },
    define: function(frameData) {
      var frame, _i, _len, _results;
      if (frameData.length) {
        _results = [];
        for (_i = 0, _len = frameData.length; _i < _len; _i++) {
          frame = frameData[_i];
          _results.push(this.generate(frame));
        }
        return _results;
      } else {
        return this.generate(frameData);
      }
    }
  };

  vendorPrefix = $.keyframe.vendorPrefix();

  animationPlayState = "animation-play-state";

  playStateRunning = "running";

  $.fn.resetKeyframe = function(callback) {
    var $el;
    $el = $(this).css(vendorPrefix + animationPlayState, playStateRunning).css(vendorPrefix + "animation", "none");
    if (callback) {
      return setTimeout(callback, 1);
    }
  };

  $.fn.pauseKeyframe = function() {
    var $el;
    return $el = $(this).css(vendorPrefix + animationPlayState, "paused");
  };

  $.fn.resumeKeyframe = function() {
    return $(this).css(vendorPrefix + animationPlayState, playStateRunning);
  };

  $.fn.playKeyframe = function(frameOptions, callback) {
    var animationcss, animationkey, defaultsOptions, delay, duration, frameOptSplit, pfx, repeat, _prefixEvent;
    if (typeof frameOptions === 'string') {
      frameOptSplit = frameOptions.trim().split(' ');
      frameOptions = {
        name: frameOptSplit[0],
        duration: parseInt(frameOptSplit[1]),
        timingFunction: frameOptSplit[2],
        delay: parseInt(frameOptSplit[3]),
        repeat: frameOptSplit[4],
        complete: callback
      };
    }
    defaultsOptions = {
      duration: 0,
      timingFunction: "ease",
      delay: 0,
      repeat: 1,
      direction: "normal",
      fillMode: "forwards",
      complete: null
    };
    frameOptions = $.extend(defaultsOptions, frameOptions);
    duration = frameOptions.duration;
    delay = frameOptions.delay;
    repeat = frameOptions.repeat;
    animationcss = "" + frameOptions.name + " " + duration + "ms " + frameOptions.timingFunction + " " + delay + "ms " + repeat + " " + frameOptions.direction + " " + frameOptions.fillMode;
    callback = frameOptions.complete;
    animationkey = vendorPrefix + "animation";
    pfx = ["webkit", "moz", "MS", "o", ""];
    _prefixEvent = function(element, type, callback) {
      var evt, p, _results;
      p = 0;
      _results = [];
      while (p < pfx.length) {
        if (!pfx[p]) {
          type = type.toLowerCase();
        }
        evt = pfx[p] + type;
        element.off(evt).on(evt, callback);
        _results.push(p++);
      }
      return _results;
    };
    return this.each(function() {
      var $el;
      $el = $(this).addClass("boostKeyframe").css(vendorPrefix + animationPlayState, playStateRunning).css(animationkey, animationcss).data("keyframeOptions", frameOptions);
      if (callback) {
        _prefixEvent($el, 'AnimationIteration', callback);
        return _prefixEvent($el, 'AnimationEnd', callback);
      }
    });
  };

  $createKeyframeStyleTag({
    id: "boost-keyframe"
  }).append(" .boostKeyframe{" + vendorPrefix + "transform:scale3d(1,1,1);}");

}).call(this);
<commit_msg>Callbacks did not work for normal (not short hand) notations, as callback was not extended in $.extend(). Callback remained null, so now it works.<commit_after>(function() {
    var $createKeyframeStyleTag, animationPlayState, playStateRunning, vendorPrefix;

    $createKeyframeStyleTag = function(params) {
	return $("<style>").attr({
	    class: "keyframe-style",
	    id: params.id,
	    type: "text/css"
	}).appendTo("head");
    };

    $.keyframe = {
	vendorPrefix: function() {
	    var ua;
	    ua = navigator.userAgent;
	    if (ua.indexOf("Opera") !== -1) {
		return "-o-";
	    } else if (ua.indexOf("MSIE") !== -1) {
		return "-ms-";
	    } else if (ua.indexOf("WebKit") !== -1) {
		return "-webkit-";
	    } else {
		return "";
	    }
	},
	isSupported: function() {
	    var animationSupport, element, pfx;
	    element = $('body').get(0);
	    animationSupport = false;
	    if (element.style.animationName) {
		animationSupport = true;
	    } else {
		pfx = this.vendorPrefix().slice(1, -1);
		if (element.style[pfx + "AnimationName"]) {
		    animationSupport = true;
		}
	    }
	    return animationSupport;
	},
	generate: function(frameData) {
	    var $elems, $frameStyle, css, frameName, property;
	    frameName = frameData.name || "";
	    css = "@" + (this.vendorPrefix()) + "keyframes " + frameName + " {";
	    for (property in frameData) {
		if (property !== "name") {
		    css += "" + property + "{" + frameData[property] + "}";
		}
	    }
	    css += "}";
	    $frameStyle = $("style#" + frameData.name);
	    if ($frameStyle.length > 0) {
		$frameStyle.html(css);
		$elems = $("*").filter(function() {
		    return this.style["" + ($.keyframe.vendorPrefix().slice(1, -1)) + "AnimationName"] === frameName;
		});
		return $elems.each(function() {
		    var $el, options;
		    $el = $(this);
		    options = $el.data("keyframeOptions");
		    return $el.resetKeyframe(function() {
			return $el.playKeyframe(options);
		    });
		});
	    } else {
		return $createKeyframeStyleTag({
		    id: frameName
		}).append(css);
	    }
	},
	define: function(frameData) {
	    var frame, _i, _len, _results;
	    if (frameData.length) {
		_results = [];
		for (_i = 0, _len = frameData.length; _i < _len; _i++) {
		    frame = frameData[_i];
		    _results.push(this.generate(frame));
		}
		return _results;
	    } else {
		return this.generate(frameData);
	    }
	}
    };

    vendorPrefix = $.keyframe.vendorPrefix();
    animationPlayState = "animation-play-state";
    playStateRunning = "running";

    $.fn.resetKeyframe = function(callback) {
	var $el;
	$el = $(this).css(vendorPrefix + animationPlayState, playStateRunning).css(vendorPrefix + "animation", "none");
	if (callback) {
	    return setTimeout(callback, 1);
	}
    };

    $.fn.pauseKeyframe = function() {
	var $el;
	return $el = $(this).css(vendorPrefix + animationPlayState, "paused");
    };

    $.fn.resumeKeyframe = function() {
	return $(this).css(vendorPrefix + animationPlayState, playStateRunning);
    };

    $.fn.playKeyframe = function(frameOptions, callback) {
	var animationcss, animationkey, defaultsOptions, delay, duration, frameOptSplit, pfx, repeat, _prefixEvent;
	if (typeof frameOptions === 'string') {
	    frameOptSplit = frameOptions.trim().split(' ');
	    frameOptions = {
		name: frameOptSplit[0],
		duration: parseInt(frameOptSplit[1]),
		timingFunction: frameOptSplit[2],
		delay: parseInt(frameOptSplit[3]),
		repeat: frameOptSplit[4],
		complete: callback
	    };
	}
	defaultsOptions = {
	    duration: 0,
	    timingFunction: "ease",
	    delay: 0,
	    repeat: 1,
	    direction: "normal",
	    fillMode: "forwards",
	    complete: callback
	};

	frameOptions = $.extend(defaultsOptions, frameOptions);

	duration = frameOptions.duration;
	delay = frameOptions.delay;
	repeat = frameOptions.repeat;
	animationcss = "" + frameOptions.name + " " + duration + "ms " + frameOptions.timingFunction + " " + delay + "ms " + repeat + " " + frameOptions.direction + " " + frameOptions.fillMode;
	callback = frameOptions.complete;
	animationkey = vendorPrefix + "animation";
	pfx = ["webkit", "moz", "MS", "o", ""];
	_prefixEvent = function(element, type, callback) {
	    var evt, p, _results;
	    p = 0;
	    _results = [];
	    while (p < pfx.length) {
		if (!pfx[p]) {
		    type = type.toLowerCase();
		}
		evt = pfx[p] + type;
		element.off(evt).on(evt, callback);
		_results.push(p++);
	    }
	    return _results;
	};
	return this.each(function() {
	    var $el;
	    $el = $(this).addClass("boostKeyframe").css(vendorPrefix + animationPlayState, playStateRunning).css(animationkey, animationcss).data("keyframeOptions", frameOptions);
	    if (callback) {
		_prefixEvent($el, 'AnimationIteration', callback);
		return _prefixEvent($el, 'AnimationEnd', callback);
	    }
	});
    };

    $createKeyframeStyleTag({
	id: "boost-keyframe"
    }).append(" .boostKeyframe{" + vendorPrefix + "transform:scale3d(1,1,1);}");

}).call(this);
<|endoftext|>