<commit_before>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import DeletedState from './enums/DeletedState';
import {camelToPascalCase, camelToSnakeCase, snakeToCamelCase} from './utils/caseConverter';
import {getTypeName} from './utils/reflection';
import {encodeQueryString} from './utils/requests';
import {fnv32a, hashObject} from './utils/hash';

/**
 * Used for keeping track of the request parameters
 */
export default class RequestParameters {
  /**
   * RequestParameters constructor
   * @param {Object} object - properties
   */
  constructor(object = {}) {
    this._watch = [];

    // Apply defaults
    RequestParameters.keys().forEach(x => this._resolve(x));

    // Apply properties
    for (const key of Object.keys(object)) {
      const Key = snakeToCamelCase(key);

      if (key[0] === '_' || !RequestParameters.keys().includes(Key)) {
        continue;
      }

      this._update(Key, object[key]);
    }
  }

  // region instance
  // region instance getters
  /**
   * Get page number
   * @returns {Number} - Page number
   * @throws TypeError
   */
  get page() {
    return this._resolve('page');
  }

  /**
   * Get rows per page
   * @returns {Number} - Per page
   * @throws TypeError
   */
  get perPage() {
    return this._resolve('perPage');
  }

  /**
   * Search query
   * @returns {Object<String, String|Array<String>>} - Query
   * @throws TypeError
   */
  get search() {
    return this._resolve('search');
  }

  /**
   * Get sort options
   * @returns {Array<String>} - Per page
   * @throws TypeError
   */
  get sort() {
    return this._resolve('sort');
  }

  /**
   * If deleted items should be shown
   * @returns {String} - Deleted items filter state
   * @see {@link DeletedState}
   */
  get deleted() {
    return this._resolve('deleted');
  }

  // endregion instance getters

  // region instance setters
  /**
   * Page number
   * @param {Number} value - Page number
   */
  set page(value) {
    this._update('page', value);
  }

  /**
   * Rows per page number
   * @param {Number} value - Per page
   */
  set perPage(value) {
    this._update('perPage', value);
  }

  /**
   * Search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  set search(value) {
    this._update('search', value);
  }

  /**
   * Sort query
   * @param {Array<String>} value - Sort query
   */
  set sort(value) {
    this._update('sort', value);
  }

  /**
   * Deleted items filter state
   * @param {String} value - Deleted items filter state
   * @see {@link DeletedState}
   */
  set deleted(value) {
    this._update('deleted', value);
  }

  // endregion instance setters
  // endregion instance

  // region static
  // region getters
  /**
   * Default page number
   * @returns {Number} - Page number
   */
  static get page() {
    return RequestParameters._page || 1;
  }

  /**
   * Default per page
   * @returns {Number} - Per page
   */
  static get perPage() {
    return RequestParameters._perPage || Number(process.env.PER_PAGE);
  }

  /**
   * Default search query
   * @returns {Object<String, String|Array<String>>} - Search query
   */
  static get search() {
    return RequestParameters._search || {};
  }

  /**
   * Default sort query
   * @returns {Array<String>} - Sort query
   */
  static get sort() {
    return RequestParameters._sort || [];
  }

  /**
   * Default deleted items filter state
   * @returns {String} -  Deleted items filter state
   */
  static get deleted() {
    return RequestParameters._deleted || process.env.SHOW_DELETED.toLowerCase();
  }

  // endregion getters

  // region setters
  /**
   * Default page number
   * @param {Number} value - Page number
   */
  static set page(value) {
    RequestParameters._page = RequestParameters._validatePage(value);
  }

  /**
   * Default per page
   * @param {Number} value - Per page
   */
  static set perPage(value) {
    RequestParameters._perPage = RequestParameters._validatePerPage(value);
  }

  /**
   * Default search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  static set search(value) {
    RequestParameters._search = RequestParameters._validateSearch(value);
  }

  /**
   * Default sort query
   * @param {Array<String>} value - Sort query
   */
  static set sort(value) {
    RequestParameters._sort = RequestParameters._validateSort(value);
  }

  /**
   * Default deleted items filter state
   * @param {String} value -  Deleted items filter state
   */
  static set deleted(value) {
    RequestParameters._deleted = RequestParameters._validateDeleted(value);
  }

  // endregion setters
  // endregion static

  // region validators
  /**
   * Validators should work the same as laravel's ::validate method. This means
   * this means that they will throw a TypeError or return a normalized result.
   */

  static _validatePage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'number' instead got '${typeof value}'`);
    }

    return Math.round(value);
  }

  static _validatePerPage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'Number' instead got '${getTypeName(value)}'`);
    }

    value = Math.round(value);
    value = Math.min(50, value); // Upper limit is 50
    value = Math.max(1, value); // Lower limit is 1

    return value;
  }

  static _validateSearch(value) {
    if (typeof value !== 'object') {
      throw new TypeError(`Expected value to be of type "Object" got "${getTypeName(value)}"`);
    }

    for (const key of Object.keys(value)) {
      if (typeof key !== 'string') {
        throw new TypeError(`Expected key to be of type "String" got "${getTypeName(key)}"`);
      }

      if (Array.isArray(value[key])) {
        if (value[key].length > 0) {
          for (const query of value[key]) {
            if (typeof query !== 'string') {
              throw new TypeError(`Expected query for "${key}" to be of type "String" got "${getTypeName(query)}"`);
            }
          }
        } else {
          // Drop empty nodes
          delete value[key];
        }
      } else if (value[key] === null) {
        delete value[key];
      } else if (typeof value[key] !== 'string') {
        throw new TypeError(`Expected query value to be of type "String" or "Array" got "${getTypeName(key)}"`);
      }
    }

    return value;
  }

  static _validateSort(value) {
    if (!(value instanceof Array)) {
      throw new TypeError(`Expected sort value to be of type "Array" got "${getTypeName(value)}"`);
    }

    // Array keys type checking
    value
      .filter(x => typeof x !== 'string')
      .forEach(x => {
        throw new TypeError(`Expected sort array values to be of type "String" got "${getTypeName(x)}"`);
      });

    // Don't do regex matching because it's something
    // we can just let the server do for us.

    return value;
  }

  static _validateDeleted(value) {
    value = value.toLowerCase();

    const possible = DeletedState.values();

    if (!possible.includes(value)) {
      throw new TypeError(`Expected deleted to be on of ${possible.join(', ')}`);
    }

    return value;
  }

  // endregion validators

  _resolve(name) {
    const _name = '_' + name;

    if (!this[_name]) {
      // Confuse esdoc
      (this || {})[_name] = RequestParameters[name];
    }

    return this[_name];
  }

  _update(name, value) {
    const _name = '_' + name;

    value = RequestParameters['_validate' + camelToPascalCase(name)](value);
    (this || {})[_name] = value; // Weird syntax confuses esdoc

    this._watch.forEach(m => m(name, value));
  }

  // region utils
  /**
   * Urlencode parameters
   * @returns {string} - HTTP query
   */
  encode() {
    const data = this.toObject();

    if (Array.isArray(data.sort)) {
      data.sort = data.sort.join(',');
    }

    return encodeQueryString(data);
  }

  /**
   * Convert to object
   * @returns {Object} - Object
   */
  toObject() {
    const data = {};

    RequestParameters
      .keys()
      .forEach(key => {
        data[camelToSnakeCase(key)] = this._resolve(key);
      });

    return data;
  }

  /**
   * Copy object
   * @returns {RequestParameters} - Copy
   */
  copy() {
    return new RequestParameters(this.toObject());
  }

  /**
   * Different parameters
   * @returns {Array<String>} - keys
   */
  static keys() {
    // enumeration is disabled for properties
    return [
      'page',
      'perPage',
      'search',
      'sort',
      'deleted',
    ];
  }

  /**
   * Watch for changes
   * @param {Function} method - Callback method
   * @param {?String} [name=null] - Property name
   * @returns {void}
   */
  watch(method, name = null) {
    if (name) {
      method = (n, v) => {
        if (n === name.toLowerCase()) {
          method(v);
        }
      };
    }

    this._watch.push(method);
  }

  token() {
    return hashObject(this.toObject());
  }
  // endregion utils
}
<commit_msg>Normalize search query<commit_after>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import DeletedState from './enums/DeletedState';
import {camelToPascalCase, camelToSnakeCase, snakeToCamelCase} from './utils/caseConverter';
import {getTypeName} from './utils/reflection';
import {encodeQueryString} from './utils/requests';
import {fnv32a, hashObject} from './utils/hash';

/**
 * Used for keeping track of the request parameters
 */
export default class RequestParameters {
  /**
   * RequestParameters constructor
   * @param {Object} object - properties
   */
  constructor(object = {}) {
    this._watch = [];

    // Apply defaults
    RequestParameters.keys().forEach(x => this._resolve(x));

    // Apply properties
    for (const key of Object.keys(object)) {
      const Key = snakeToCamelCase(key);

      if (key[0] === '_' || !RequestParameters.keys().includes(Key)) {
        continue;
      }

      this._update(Key, object[key]);
    }
  }

  // region instance
  // region instance getters
  /**
   * Get page number
   * @returns {Number} - Page number
   * @throws TypeError
   */
  get page() {
    return this._resolve('page');
  }

  /**
   * Get rows per page
   * @returns {Number} - Per page
   * @throws TypeError
   */
  get perPage() {
    return this._resolve('perPage');
  }

  /**
   * Search query
   * @returns {Object<String, String|Array<String>>} - Query
   * @throws TypeError
   */
  get search() {
    return this._resolve('search');
  }

  /**
   * Get sort options
   * @returns {Array<String>} - Per page
   * @throws TypeError
   */
  get sort() {
    return this._resolve('sort');
  }

  /**
   * If deleted items should be shown
   * @returns {String} - Deleted items filter state
   * @see {@link DeletedState}
   */
  get deleted() {
    return this._resolve('deleted');
  }

  // endregion instance getters

  // region instance setters
  /**
   * Page number
   * @param {Number} value - Page number
   */
  set page(value) {
    this._update('page', value);
  }

  /**
   * Rows per page number
   * @param {Number} value - Per page
   */
  set perPage(value) {
    this._update('perPage', value);
  }

  /**
   * Search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  set search(value) {
    this._update('search', value);
  }

  /**
   * Sort query
   * @param {Array<String>} value - Sort query
   */
  set sort(value) {
    this._update('sort', value);
  }

  /**
   * Deleted items filter state
   * @param {String} value - Deleted items filter state
   * @see {@link DeletedState}
   */
  set deleted(value) {
    this._update('deleted', value);
  }

  // endregion instance setters
  // endregion instance

  // region static
  // region getters
  /**
   * Default page number
   * @returns {Number} - Page number
   */
  static get page() {
    return RequestParameters._page || 1;
  }

  /**
   * Default per page
   * @returns {Number} - Per page
   */
  static get perPage() {
    return RequestParameters._perPage || Number(process.env.PER_PAGE);
  }

  /**
   * Default search query
   * @returns {Object<String, String|Array<String>>} - Search query
   */
  static get search() {
    return RequestParameters._search || {};
  }

  /**
   * Default sort query
   * @returns {Array<String>} - Sort query
   */
  static get sort() {
    return RequestParameters._sort || [];
  }

  /**
   * Default deleted items filter state
   * @returns {String} -  Deleted items filter state
   */
  static get deleted() {
    return RequestParameters._deleted || process.env.SHOW_DELETED.toLowerCase();
  }

  // endregion getters

  // region setters
  /**
   * Default page number
   * @param {Number} value - Page number
   */
  static set page(value) {
    RequestParameters._page = RequestParameters._validatePage(value);
  }

  /**
   * Default per page
   * @param {Number} value - Per page
   */
  static set perPage(value) {
    RequestParameters._perPage = RequestParameters._validatePerPage(value);
  }

  /**
   * Default search query
   * @param {Object<String, String|Array<String>>} value - Search query
   */
  static set search(value) {
    RequestParameters._search = RequestParameters._validateSearch(value);
  }

  /**
   * Default sort query
   * @param {Array<String>} value - Sort query
   */
  static set sort(value) {
    RequestParameters._sort = RequestParameters._validateSort(value);
  }

  /**
   * Default deleted items filter state
   * @param {String} value -  Deleted items filter state
   */
  static set deleted(value) {
    RequestParameters._deleted = RequestParameters._validateDeleted(value);
  }

  // endregion setters
  // endregion static

  // region validators
  /**
   * Validators should work the same as laravel's ::validate method. This means
   * this means that they will throw a TypeError or return a normalized result.
   */

  static _validatePage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'number' instead got '${typeof value}'`);
    }

    return Math.round(value);
  }

  static _validatePerPage(value) {
    if (typeof value !== 'number') {
      throw new TypeError(`Expected page to be of type 'Number' instead got '${getTypeName(value)}'`);
    }

    value = Math.round(value);
    value = Math.min(50, value); // Upper limit is 50
    value = Math.max(1, value); // Lower limit is 1

    return value;
  }

  static _validateSearch(value) {
    if (typeof value !== 'object') {
      throw new TypeError(`Expected value to be of type "Object" got "${getTypeName(value)}"`);
    }

    // Normalization macro
    const normalize = x => typeof x === 'number' ? x.toString() : x;

    for (let key of Object.keys(value)) {
      key = normalize(key);
      value[key] = normalize(value[key]);

      if (typeof key !== 'string') {
        throw new TypeError(`Expected key to be of type "String" got "${getTypeName(key)}"`);
      }

      if (Array.isArray(value[key])) {
        if (value[key].length > 0) {
          for (const query of value[key]) {
            if (typeof query !== 'string') {
              throw new TypeError(`Expected query for "${key}" to be of type "String" got "${getTypeName(query)}"`);
            }
          }
        } else {
          // Drop empty nodes
          delete value[key];
        }
      } else if (value[key] === null) {
        delete value[key];
      } else if (typeof value[key] !== 'string') {
        throw new TypeError(`Expected query value to be of type "String" or "Array" got "${getTypeName(value[key])}"`);
      }
    }

    return value;
  }

  static _validateSort(value) {
    if (!(value instanceof Array)) {
      throw new TypeError(`Expected sort value to be of type "Array" got "${getTypeName(value)}"`);
    }

    // Array keys type checking
    value
      .filter(x => typeof x !== 'string')
      .forEach(x => {
        throw new TypeError(`Expected sort array values to be of type "String" got "${getTypeName(x)}"`);
      });

    // Don't do regex matching because it's something
    // we can just let the server do for us.

    return value;
  }

  static _validateDeleted(value) {
    value = value.toLowerCase();

    const possible = DeletedState.values();

    if (!possible.includes(value)) {
      throw new TypeError(`Expected deleted to be on of ${possible.join(', ')}`);
    }

    return value;
  }

  // endregion validators

  _resolve(name) {
    const _name = '_' + name;

    if (!this[_name]) {
      // Confuse esdoc
      (this || {})[_name] = RequestParameters[name];
    }

    return this[_name];
  }

  _update(name, value) {
    const _name = '_' + name;

    value = RequestParameters['_validate' + camelToPascalCase(name)](value);
    (this || {})[_name] = value; // Weird syntax confuses esdoc

    this._watch.forEach(m => m(name, value));
  }

  // region utils
  /**
   * Urlencode parameters
   * @returns {string} - HTTP query
   */
  encode() {
    const data = this.toObject();

    if (Array.isArray(data.sort)) {
      data.sort = data.sort.join(',');
    }

    return encodeQueryString(data);
  }

  /**
   * Convert to object
   * @returns {Object} - Object
   */
  toObject() {
    const data = {};

    RequestParameters
      .keys()
      .forEach(key => {
        data[camelToSnakeCase(key)] = this._resolve(key);
      });

    return data;
  }

  /**
   * Copy object
   * @returns {RequestParameters} - Copy
   */
  copy() {
    return new RequestParameters(this.toObject());
  }

  /**
   * Different parameters
   * @returns {Array<String>} - keys
   */
  static keys() {
    // enumeration is disabled for properties
    return [
      'page',
      'perPage',
      'search',
      'sort',
      'deleted',
    ];
  }

  /**
   * Watch for changes
   * @param {Function} method - Callback method
   * @param {?String} [name=null] - Property name
   * @returns {void}
   */
  watch(method, name = null) {
    if (name) {
      method = (n, v) => {
        if (n === name.toLowerCase()) {
          method(v);
        }
      };
    }

    this._watch.push(method);
  }

  token() {
    return hashObject(this.toObject());
  }
  // endregion utils
}
<|endoftext|>


<commit_before>import { useState, useEffect, useReducer, useCallback } from 'react'
import { concatTranscripts, commandToRegExp, compareTwoStringsUsingDiceCoefficient } from './utils'
import { clearTrancript, appendTrancript } from './actions'
import { transcriptReducer } from './reducers'
import RecognitionManager from './RecognitionManager'

const useSpeechRecognition = ({
  transcribing = true,
  clearTranscriptOnListen = true,
  commands = []
} = {}) => {
  const [recognitionManager] = useState(SpeechRecognition.getRecognitionManager())
  const [{ interimTranscript, finalTranscript }, dispatch] = useReducer(transcriptReducer, {
    interimTranscript: recognitionManager.interimTranscript,
    finalTranscript: ''
  })
  const [listening, setListening] = useState(recognitionManager.listening)

  const clearTranscript = () => {
    dispatch(clearTrancript())
  }

  const resetTranscript = () => {
    recognitionManager.resetTranscript()
    clearTranscript()
  }

  const matchCommands = useCallback(
    (newInterimTranscript, newFinalTranscript) => {
      commands.forEach(({ command, callback, matchInterim = false, isFuzzyMatch = false, fuzzyMatchingThreshold = 0.8 }) => {
        const input = !newFinalTranscript && matchInterim
          ? newInterimTranscript.trim()
          : newFinalTranscript.trim()
        if (isFuzzyMatch) {
          const commandToString = (typeof command === 'object') ? command.toString() : command
          const commandWithoutSpecials = commandToString
            .replace(/[&/\\#,+()!$~%.'":*?<>{}]/g, '')
            .replace(/  +/g, ' ')
            .trim()
          const howSimilar = compareTwoStringsUsingDiceCoefficient(commandWithoutSpecials, input)
          if (howSimilar >= fuzzyMatchingThreshold) {
            callback(commandWithoutSpecials, input, howSimilar, { resetTranscript })
          }
        } else {
          const pattern = commandToRegExp(command)
          const result = pattern.exec(input)
          if (result) {
            const parameters = result.slice(1)
            callback(...parameters, { resetTranscript })
          }
        }
      })
    }, [commands]
  )

  const handleTranscriptChange = useCallback(
    (newInterimTranscript, newFinalTranscript) => {
      matchCommands(newInterimTranscript, newFinalTranscript)
      if (transcribing) {
        dispatch(appendTrancript(newInterimTranscript, newFinalTranscript))
      }
    }, [matchCommands, transcribing]
  )

  const handleClearTranscript = useCallback(
    () => {
      if (clearTranscriptOnListen) {
        clearTranscript()
      }
    }, [clearTranscriptOnListen]
  )

  useEffect(() => {
    const id = SpeechRecognition.counter
    SpeechRecognition.counter += 1
    const callbacks = {
      onListeningChange: setListening,
      onTranscriptChange: handleTranscriptChange,
      onClearTranscript: handleClearTranscript
    }
    recognitionManager.subscribe(id, callbacks)

    return () => {
      recognitionManager.unsubscribe(id)
    }
  }, [
    transcribing,
    clearTranscriptOnListen,
    recognitionManager,
    handleTranscriptChange,
    handleClearTranscript
  ])

  const transcript = concatTranscripts(finalTranscript, interimTranscript)
  return {
    transcript,
    interimTranscript,
    finalTranscript,
    listening,
    resetTranscript
  }
}

let recognitionManager
const SpeechRecognition = {
  counter: 0,
  getRecognitionManager: () => {
    if (!recognitionManager) {
      recognitionManager = new RecognitionManager()
    }
    return recognitionManager
  },
  getRecognition: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    return recognitionManager.getRecognition()
  },
  startListening: async ({ continuous, language } = {}) => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    await recognitionManager.startListening({ continuous, language })
  },
  stopListening: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    recognitionManager.stopListening()
  },
  abortListening: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    recognitionManager.abortListening()
  },
  browserSupportsSpeechRecognition: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    return recognitionManager.browserSupportsSpeechRecognition
  }
}

export { useSpeechRecognition }
export default SpeechRecognition
<commit_msg>[Green] Command callback can reset transcript<commit_after>import { useState, useEffect, useReducer, useCallback } from 'react'
import { concatTranscripts, commandToRegExp, compareTwoStringsUsingDiceCoefficient } from './utils'
import { clearTrancript, appendTrancript } from './actions'
import { transcriptReducer } from './reducers'
import RecognitionManager from './RecognitionManager'

const useSpeechRecognition = ({
  transcribing = true,
  clearTranscriptOnListen = true,
  commands = []
} = {}) => {
  const [recognitionManager] = useState(SpeechRecognition.getRecognitionManager())
  const [{ interimTranscript, finalTranscript }, dispatch] = useReducer(transcriptReducer, {
    interimTranscript: recognitionManager.interimTranscript,
    finalTranscript: ''
  })
  const [listening, setListening] = useState(recognitionManager.listening)

  const clearTranscript = () => {
    dispatch(clearTrancript())
  }

  const resetTranscript = useCallback(() => {
    recognitionManager.resetTranscript()
    clearTranscript()
  }, [recognitionManager])

  const matchCommands = useCallback(
    (newInterimTranscript, newFinalTranscript) => {
      commands.forEach(({ command, callback, matchInterim = false, isFuzzyMatch = false, fuzzyMatchingThreshold = 0.8 }) => {
        const input = !newFinalTranscript && matchInterim
          ? newInterimTranscript.trim()
          : newFinalTranscript.trim()
        if (isFuzzyMatch) {
          const commandToString = (typeof command === 'object') ? command.toString() : command
          const commandWithoutSpecials = commandToString
            .replace(/[&/\\#,+()!$~%.'":*?<>{}]/g, '')
            .replace(/  +/g, ' ')
            .trim()
          const howSimilar = compareTwoStringsUsingDiceCoefficient(commandWithoutSpecials, input)
          if (howSimilar >= fuzzyMatchingThreshold) {
            callback(commandWithoutSpecials, input, howSimilar, { resetTranscript })
          }
        } else {
          const pattern = commandToRegExp(command)
          const result = pattern.exec(input)
          if (result) {
            const parameters = result.slice(1)
            callback(...parameters, { resetTranscript })
          }
        }
      })
    }, [commands, resetTranscript]
  )

  const handleTranscriptChange = useCallback(
    (newInterimTranscript, newFinalTranscript) => {
      if (transcribing) {
        dispatch(appendTrancript(newInterimTranscript, newFinalTranscript))
      }
      matchCommands(newInterimTranscript, newFinalTranscript)
    }, [matchCommands, transcribing]
  )

  const handleClearTranscript = useCallback(
    () => {
      if (clearTranscriptOnListen) {
        clearTranscript()
      }
    }, [clearTranscriptOnListen]
  )

  useEffect(() => {
    const id = SpeechRecognition.counter
    SpeechRecognition.counter += 1
    const callbacks = {
      onListeningChange: setListening,
      onTranscriptChange: handleTranscriptChange,
      onClearTranscript: handleClearTranscript
    }
    recognitionManager.subscribe(id, callbacks)

    return () => {
      recognitionManager.unsubscribe(id)
    }
  }, [
    transcribing,
    clearTranscriptOnListen,
    recognitionManager,
    handleTranscriptChange,
    handleClearTranscript
  ])

  const transcript = concatTranscripts(finalTranscript, interimTranscript)
  return {
    transcript,
    interimTranscript,
    finalTranscript,
    listening,
    resetTranscript
  }
}

let recognitionManager
const SpeechRecognition = {
  counter: 0,
  getRecognitionManager: () => {
    if (!recognitionManager) {
      recognitionManager = new RecognitionManager()
    }
    return recognitionManager
  },
  getRecognition: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    return recognitionManager.getRecognition()
  },
  startListening: async ({ continuous, language } = {}) => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    await recognitionManager.startListening({ continuous, language })
  },
  stopListening: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    recognitionManager.stopListening()
  },
  abortListening: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    recognitionManager.abortListening()
  },
  browserSupportsSpeechRecognition: () => {
    const recognitionManager = SpeechRecognition.getRecognitionManager()
    return recognitionManager.browserSupportsSpeechRecognition
  }
}

export { useSpeechRecognition }
export default SpeechRecognition
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var View = require('view');
var template = require('./template');
var truncate = require('truncate');
var config = require('config');
var Participants = require('participants-box');
var ProposalClauses = require('proposal-clauses');
var o = require('dom');

/**
 * Expose ProposalArticle
 */

module.exports = ProposalArticle;

/**
 * Creates a new proposal-article view
 * from proposals object.
 *
 * @param {Object} proposal proposal's object data
 * @return {ProposalArticle} `ProposalArticle` instance.
 * @api public
 */

function ProposalArticle (proposal) {
  if (!(this instanceof ProposalArticle)) {
    return new ProposalArticle(proposal);
  };

  this.proposal = proposal;
  this.clauses = proposal.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  });

  var baseUrl = config.protocol + "://" + config.host + (config.publicPort ? (":" + config.publicPort) : "");

  View.call(this, template, {
    proposal: proposal,
    clauses: this.clauses,
    baseUrl: baseUrl,
    truncate: truncate
  });

  this.participants = new Participants(proposal.participants || []);
  this.participants.appendTo(this.find('.participants')[0]);
  this.participants.fetch();

  this.proposalClauses = new ProposalClauses(proposal);
  this.proposalClauses.appendTo('.clauses');

  commentable(proposal.summary, proposal.id).appendTo(this.find('.summary')[0]);
}

/**
 * Inherit from View
 */

View(ProposalArticle);

/**
 * Turn on event handlers on this view
 */

ProposalArticle.prototype.switchOn = function() {
  this.bind('click', '.clauses a.read-more', 'showclauses');
}

ProposalArticle.prototype.showclauses = function(ev) {
  ev.preventDefault();

  this.find('.clauses .clause.hide').removeClass('hide');
  this.unbind('click', '.clauses a.read-more', 'showclauses');
  this.find('.clauses a.read-more').remove();
}

function commentable(els, proposalId) {
  if ('string' === typeof els) els = o(els);
  return els
    .find('div')
    .each(function(div, i) {
      div.attr('data-section-id', proposalId + '-' + i)
        .addClass('commentable-section');
    })
}
<commit_msg>[admin-law-form] Fixed richtext error when rendering legacy law text #438<commit_after>
/**
 * Module dependencies.
 */

var View = require('view');
var template = require('./template');
var truncate = require('truncate');
var config = require('config');
var Participants = require('participants-box');
var ProposalClauses = require('proposal-clauses');
var o = require('dom');

/**
 * Expose ProposalArticle
 */

module.exports = ProposalArticle;

/**
 * Creates a new proposal-article view
 * from proposals object.
 *
 * @param {Object} proposal proposal's object data
 * @return {ProposalArticle} `ProposalArticle` instance.
 * @api public
 */

function ProposalArticle (proposal) {
  if (!(this instanceof ProposalArticle)) {
    return new ProposalArticle(proposal);
  };

  this.proposal = proposal;
  this.clauses = proposal.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  });

  var baseUrl = config.protocol + "://" + config.host + (config.publicPort ? (":" + config.publicPort) : "");

  View.call(this, template, {
    proposal: proposal,
    clauses: this.clauses,
    baseUrl: baseUrl,
    truncate: truncate
  });

  this.participants = new Participants(proposal.participants || []);
  this.participants.appendTo(this.find('.participants')[0]);
  this.participants.fetch();

  this.proposalClauses = new ProposalClauses(proposal);
  this.proposalClauses.appendTo('.clauses');

  this.summary = this.find('.summary').html(proposal.summary);
  commentable(this.summary, proposal.id);
}

/**
 * Inherit from View
 */

View(ProposalArticle);

/**
 * Turn on event handlers on this view
 */

ProposalArticle.prototype.switchOn = function() {
  this.bind('click', '.clauses a.read-more', 'showclauses');
}

ProposalArticle.prototype.showclauses = function(ev) {
  ev.preventDefault();

  this.find('.clauses .clause.hide').removeClass('hide');
  this.unbind('click', '.clauses a.read-more', 'showclauses');
  this.find('.clauses a.read-more').remove();
}

function commentable(els, proposalId) {
  var divs = els.find('div');

  if (!divs || divs.length === 0) {
    // Old-fashioned law format
    var paragraphs = els.html().split('\n');
    var html = '';
    paragraphs.forEach(function(val, i) {
      html += '
'
        + val + '
';
    });
    els.html(html);
  } else {
    // New law format
    divs.each(function(div, i) {
      div.attr('data-section-id', proposalId + '-' + i)
        .addClass('commentable-section');
    });
  }
}
<|endoftext|>


<commit_before>const Discord = require("discord.js"),
    beheeyem = new Discord.Client(),
    fs = require("fs"),
    jsonfile = require("jsonfile"),
    path = require("path"),
    request = require("request"),
    colors = require('colors');
var config = require('./config.js'),
    imageCheck;

console.log("Starting Beheeyem...");

var commands = loadCommands();

beheeyem.on("ready", function() {
    console.log(("Beheeyem is active! Currently serving in " + String(beheeyem.guilds.size).white + " guilds.\n".green).bold);
    beheeyem.user.setGame("b.help");
    try {
        request.post("https://bots.discord.pw/api/bots/" + beheeyem.user.id + "/stats", {
            server_count: beheeyem.guilds.size
        });
    } catch (err) {
        console.log("Could not update server count on Discord Bots website.".yellow);
    }

});

function loadCommands() {
    console.log('Loading commands...'.cyan)
    var commands = {};
    let errCount = 0;
    cmdfiles = fs.readdirSync('./commands/');
    cmdfiles.forEach(filename => {
        var cmdName = filename.split('.')[0];
        try {
            commands[cmdName] = require('./commands/' + filename);
            console.log('Loaded '.green + cmdName.yellow.bold);
        } catch (err) {
            if (err) {
                errCount++;
                console.log('Error in '.red + cmdName.yellow + '!'.red + '\n' + err.stack);
            }
        }
    });
    console.log('Loaded commands with '.cyan + (errCount > 0 ? errCount.toString().red : 'no'.green) + ` error${errCount == 1? '' : 's'}!`.cyan);
    return commands;
}

beheeyem.on("message", msg => {
    if (msg.author.id != beheeyem.user.id && !msg.author.bot) {
        if (msg.content.startsWith(config.prefix)) {
            var commandstring = msg.content.substring(config.prefix.length);
            var cmd = commandstring.split(" ")[0];
            var args = commandstring.substring(cmd.length + 1);

            if (commands[cmd]) {
                commands[cmd].action(msg, args, beheeyem);
            } else if (cmd == "obtain") {
                msg.channel.send("Honestly, just use Bulbapedia. The encounter data on the web is so inconsistent and undreadable that there's no way I could create an obtainability command. Sorry about that. 🙁");
            } else if (cmd == "deathbird") {
                msg.channel.send('', {
                    file: "https://i.imgur.com/pIxQQXA.png",
                    name: "DEATHBIRD.png"
                });
            } else if (cmd == "youtried") {
                msg.channel.send('', {
                    file: "https://i.imgur.com/bAxMdQ0.png",
                    name: "Filename.jpeg.gif.webp.mp4.exe.bat.sh.app.png"
                });
            } else if (msg.author.id == 120887602395086848) {
                if (cmd == "addnonya") {
                    var nonyas = require("./data/nonyas.json");
                    nonyas.nonyas.push(args);
                    jsonfile.writeFileSync("./data/nonyas.json", nonyas);
                    msg.channel.send("👌🏾 Sucessfully added Nonya!");
                } else if (cmd == 'eval') {
                    try {
                        msg.channel.sendMessage("", {
                            embed: {
                                title: '🖥 JavaScript Eval',
                                fields: [{
                                        name: "Input",
                                        value: args
                                    },
                                    {
                                        name: "Output",
                                        value: eval(args)
                                    }
                                ],
                                color: 5561189
                            }
                        });
                    } catch (err) {
                        msg.channel.sendMessage("", {
                            embed: {
                                title: '⚠ Error',
                                fields: [{
                                        name: "Input",
                                        value: args
                                    },
                                    {
                                        name: "Error",
                                        value: err.toString()
                                    }
                                ],
                                color: 16724015
                            }
                        });
                    }
                }
            }
        }
        imageCheck = false;
        checkItalics(msg);
    }
});

beheeyem.login(config.token);

beheeyem.on("guildCreate", (guild) => {
    try {
        request.post({
            url: "https://bots.discord.pw/api/bots/" + beheeyem.user.id + "/stats",
            headers: {
                authorization: config.dbotsToken
            }
        }, {
            server_count: beheeyem.guilds.size
        });
    } catch (err) {
        console.log("Could not update server count on Discord Bots website.".yellow);
    }
});

function capitalizeFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

function checkItalics(msg) {
    let isShiny = false;
    var isFound = false;
    let urlBuild = 'https://play.pokemonshowdown.com/sprites/xyani/';
    var asteriskSplit = msg.content.replace('#', '').replace('?', '').split("*");
    var pokeName;
    for (var i = 1; i < asteriskSplit.length - 1; i++) {
        pokeName = asteriskSplit[i].toLowerCase();
        if (pokeName.indexOf('shiny') != -1) {
            isShiny = true;
            pokeName = pokeName.replace(' shiny', '').replace('shiny ', '').replace('-shiny', '').replace('shiny-', '').replace('shiny', '');
        }
        pokeName = pokeName.replace(" ", "-");
        if (isShiny) urlBuild = 'https://play.pokemonshowdown.com/sprites/xyani-shiny/';
        request(urlBuild + pokeName + ".gif", (err, response) => {;
            if (!err) {
                if (response.statusCode == 200) {
                    msg.channel.send('', {
                        file: response.request.href
                    });
                    isFound = true;
                }
            }
        });
        if (isFound) break;
    }
    if (!isFound) {
        var underSplit = msg.content.replace('#', '').replace('?', '').split("_");
        var pokeName;
        for (var i = 1; i < underSplit.length - 1; i++) {
            pokeName = underSplit[i].toLowerCase();
            if (pokeName.indexOf('shiny') != -1) {
                isShiny = true;
                pokeName = pokeName.replace(' shiny', '').replace('shiny ', '').replace('-shiny', '').replace('shiny-', '').replace('shiny', '');
            }
            pokeName = pokeName.replace(" ", "-");
            if (isShiny) urlBuild = 'https://play.pokemonshowdown.com/sprites/xyani-shiny/';
            request(urlBuild + pokeName + ".gif", (err, response) => {
                if (!err) {
                    if (response.statusCode == 200) {
                        isFound == true;
                        msg.channel.send({
                            file: response.request.href
                        });
                    }
                }
            });
            if (isFound) break;
        }
    }
}<commit_msg>Added minimal error logging<commit_after>const Discord = require("discord.js"),
    beheeyem = new Discord.Client(),
    fs = require("fs"),
    jsonfile = require("jsonfile"),
    path = require("path"),
    request = require("request"),
    colors = require('colors');
var config = require('./config.js'),
    imageCheck;

console.log("Starting Beheeyem...");

var commands = loadCommands();

beheeyem.on("ready", function() {
    console.log(("Beheeyem is active! Currently serving in " + String(beheeyem.guilds.size).white + " guilds.\n".green).bold);
    beheeyem.user.setGame("b.help");
    try {
        request.post("https://bots.discord.pw/api/bots/" + beheeyem.user.id + "/stats", {
            server_count: beheeyem.guilds.size
        });
    } catch (err) {
        console.log("Could not update server count on Discord Bots website.".yellow);
    }

});

function loadCommands() {
    console.log('Loading commands...'.cyan)
    var commands = {};
    let errCount = 0;
    cmdfiles = fs.readdirSync('./commands/');
    cmdfiles.forEach(filename => {
        var cmdName = filename.split('.')[0];
        try {
            commands[cmdName] = require('./commands/' + filename);
            console.log('Loaded '.green + cmdName.yellow.bold);
        } catch (err) {
            if (err) {
                errCount++;
                console.log('Error in '.red + cmdName.yellow + '!'.red + '\n' + err.stack);
            }
        }
    });
    console.log('Loaded commands with '.cyan + (errCount > 0 ? errCount.toString().red : 'no'.green) + ` error${errCount == 1? '' : 's'}!`.cyan);
    return commands;
}

beheeyem.on("message", msg => {
    if (msg.author.id != beheeyem.user.id && !msg.author.bot) {
        if (msg.content.startsWith(config.prefix)) {
            var commandstring = msg.content.substring(config.prefix.length);
            var cmd = commandstring.split(" ")[0];
            var args = commandstring.substring(cmd.length + 1);

            if (commands[cmd]) {
                try {
                    commands[cmd].action(msg, args, beheeyem);
                } catch (err) {
                    console.error(err);
                }
            } else if (cmd == "obtain") {
                msg.channel.send("Honestly, just use Bulbapedia. The encounter data on the web is so inconsistent and undreadable that there's no way I could create an obtainability command. Sorry about that. 🙁");
            } else if (cmd == "deathbird") {
                msg.channel.send('', {
                    file: "https://i.imgur.com/pIxQQXA.png",
                    name: "DEATHBIRD.png"
                });
            } else if (cmd == "youtried") {
                msg.channel.send('', {
                    file: "https://i.imgur.com/bAxMdQ0.png",
                    name: "Filename.jpeg.gif.webp.mp4.exe.bat.sh.app.png"
                });
            } else if (msg.author.id == 120887602395086848) {
                if (cmd == "addnonya") {
                    var nonyas = require("./data/nonyas.json");
                    nonyas.nonyas.push(args);
                    jsonfile.writeFileSync("./data/nonyas.json", nonyas);
                    msg.channel.send("👌🏾 Sucessfully added Nonya!");
                } else if (cmd == 'eval') {
                    try {
                        msg.channel.sendMessage("", {
                            embed: {
                                title: '🖥 JavaScript Eval',
                                fields: [{
                                        name: "Input",
                                        value: args
                                    },
                                    {
                                        name: "Output",
                                        value: eval(args)
                                    }
                                ],
                                color: 5561189
                            }
                        });
                    } catch (err) {
                        msg.channel.sendMessage("", {
                            embed: {
                                title: '⚠ Error',
                                fields: [{
                                        name: "Input",
                                        value: args
                                    },
                                    {
                                        name: "Error",
                                        value: err.toString()
                                    }
                                ],
                                color: 16724015
                            }
                        });
                    }
                }
            }
        }
        imageCheck = false;
        checkItalics(msg);
    }
});

beheeyem.login(config.token);

beheeyem.on("guildCreate", (guild) => {
    try {
        request.post({
            url: "https://bots.discord.pw/api/bots/" + beheeyem.user.id + "/stats",
            headers: {
                authorization: config.dbotsToken
            }
        }, {
            server_count: beheeyem.guilds.size
        });
    } catch (err) {
        console.log("Could not update server count on Discord Bots website.".yellow);
    }
});

function capitalizeFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

function checkItalics(msg) {
    let isShiny = false;
    var isFound = false;
    let urlBuild = 'https://play.pokemonshowdown.com/sprites/xyani/';
    var asteriskSplit = msg.content.replace('#', '').replace('?', '').split("*");
    var pokeName;
    for (var i = 1; i < asteriskSplit.length - 1; i++) {
        pokeName = asteriskSplit[i].toLowerCase();
        if (pokeName.indexOf('shiny') != -1) {
            isShiny = true;
            pokeName = pokeName.replace(' shiny', '').replace('shiny ', '').replace('-shiny', '').replace('shiny-', '').replace('shiny', '');
        }
        pokeName = pokeName.replace(" ", "-");
        if (isShiny) urlBuild = 'https://play.pokemonshowdown.com/sprites/xyani-shiny/';
        request(urlBuild + pokeName + ".gif", (err, response) => {;
            if (!err) {
                if (response.statusCode == 200) {
                    msg.channel.send('', {
                        file: response.request.href
                    });
                    isFound = true;
                }
            }
        });
        if (isFound) break;
    }
    if (!isFound) {
        var underSplit = msg.content.replace('#', '').replace('?', '').split("_");
        var pokeName;
        for (var i = 1; i < underSplit.length - 1; i++) {
            pokeName = underSplit[i].toLowerCase();
            if (pokeName.indexOf('shiny') != -1) {
                isShiny = true;
                pokeName = pokeName.replace(' shiny', '').replace('shiny ', '').replace('-shiny', '').replace('shiny-', '').replace('shiny', '');
            }
            pokeName = pokeName.replace(" ", "-");
            if (isShiny) urlBuild = 'https://play.pokemonshowdown.com/sprites/xyani-shiny/';
            request(urlBuild + pokeName + ".gif", (err, response) => {
                if (!err) {
                    if (response.statusCode == 200) {
                        isFound == true;
                        msg.channel.send({
                            file: response.request.href
                        });
                    }
                }
            });
            if (isFound) break;
        }
    }
}<|endoftext|>


<commit_before>"use strict";

const EventEmitter = require("events").EventEmitter;
const util = require("util");

// Use the MarathonEventBusClient
const MarathonEventBusClient = require("marathon-event-bus-client");

// Use the SlackHandler
const SlackHandler = require("./SlackHandler");

/**
 * Represents a Marathon Slack Bridge
 * @constructor
 * @param {object} options - The option map object.
 */
function MarathonSlackBridge (options) {

    if (!(this instanceof MarathonSlackBridge)) {
        return new MarathonSlackBridge(options);
    }

    // Inherit from EventEmitter
    EventEmitter.call(this);

    let self = this;

    // Define Marathon options
    let marathonOptions = {
        marathonHost: options.marathonHost || process.env.MARATHON_HOST || "master.mesos",
        marathonPort: options.marathonPort || process.env.MARATHON_PORT || 8080,
        marathonProtocol: options.marathonProtocol || process.env.MARATHON_PROTOCOL || "http"
    };

    // Define event types
    self.eventTypes = [];

    // RegExes for filtering on appId
    self.appIdRegExes = [];

    // Overwrite default event types if defined via env var
    if (process.env.EVENT_TYPES) {
        // Use environment variable
        if (process.env.EVENT_TYPES.indexOf(",") > -1) {
            self.eventTypes = process.env.EVENT_TYPES.split(",");
        } else {
            self.eventTypes = [process.env.EVENT_TYPES];
        }
    } else {
        self.eventTypes = ["deployment_info", "deployment_success", "deployment_failed", "deployment_step_success", "deployment_step_failure", "group_change_success", "group_change_failed", "failed_health_check_event", "health_status_changed_event", "unhealthy_task_kill_event"]
    }

    // Set the event types for the marathonOptions
    marathonOptions.eventTypes = self.eventTypes;

    // Handle appId RegExes
    if (process.env.APP_ID_REGEXES) {
        // Use environment variable
        if (process.env.APP_ID_REGEXES.indexOf(",") > -1) {
            process.env.APP_ID_REGEXES.split(",").forEach(function (regExp) {
                try {
                    self.appIdRegExes.push(new RegExp(regExp.toString(), "i"));
                } catch(e) {
                    console.log("The provided RegExp " + regExp + " isn't a valid regular expression!");
                }
            });
        } else {
            try {
                self.appIdRegExes.push(new RegExp(regExp.toString(), "i"));
            } catch(e) {
                console.log("The provided RegExp " + regExp + " isn't a valid regular expression!");
            }
        }
    }

    // Health check status code
    self.healthCheckStatusCode = 503;

    // Instantiate SlackHandler
    self.slackHandler = new SlackHandler({
        slackWebHook: options.slackWebHook || process.env.SLACK_WEBHOOK_URL,
        slackChannel: options.slackChannel || process.env.SLACK_CHANNEL || "#marathon",
        slackBotName: options.slackBotName || process.env.SLACK_BOT_NAME || "Marathon Event Bot"
    });

    // Forward error events
    self.slackHandler.on("error", function(error) {
        self.emit("error", {
            timestamp: new Date().getTime(),
            message: "Error from SlackHandler: " + error
        });
    });

    // Forward sent_message events
    self.slackHandler.on("sent_message", function(message) {
        self.emit("sent_message", {
            timestamp: new Date().getTime(),
            message: message
        });
    });

    // Forward received_reply events
    self.slackHandler.on("received_reply", function(reply) {
        self.emit("received_reply", {
            timestamp: new Date().getTime(),
            message: reply
        });
    });

    marathonOptions.handlers = {};

    // Populate handler functions
    self.eventTypes.forEach(function (eventType) {
        // Set handler function
        marathonOptions.handlers[eventType] = function (type, data) {
            // Forward marathon_event events
            self.emit("marathon_event", {
                timestamp: new Date().getTime(),
                eventType: type,
                data: data
            });
            // Apply filter conditions
            if (self.filterEventsByAppId(data)) {
                // Render & send message
                self.slackHandler.sendMessage(self.slackHandler.renderMessage({ type: type, data: data }));
            }
        }
    });

    // Create MarathonEventBusClient instance
    self.mebc = new MarathonEventBusClient(marathonOptions);

    // Wait for "connected" event
    self.mebc.on("subscribed", function () {
        // Service is available
        self.healthCheckStatusCode = 200;
        // Emit subscribed event
        self.emit("subscribed", {
            timestamp: new Date().getTime(),
            message: "Subscribed to the Marathon Event Bus"
        });
    });

    // Wait for "unsubscribed" event
    self.mebc.on("unsubscribed", function () {
        // Service is unavailable
        self.healthCheckStatusCode = 503;
        // Forward unsubscribed event
        self.emit("unsubscribed", {
            timestamp: new Date().getTime(),
            message: "Unsubscribed from the Marathon Event Bus"
        });

    });

    // Catch error events
    self.mebc.on("error", function (errorObj) {
        // Forward error event
        self.emit("error", {
            timestamp: errorObj.timestamp,
            message: errorObj.error
        });
    });

}

// Inherit from EventEmitter
util.inherits(MarathonSlackBridge, EventEmitter);

// Wrapper for starting to listen to the Marathon Event Bus
MarathonSlackBridge.prototype.start = function () {
    // Subscribe to Marathon Event Bus
    this.mebc.subscribe();
};

// Wrapper for teardown
MarathonSlackBridge.prototype.stop = function () {
    // Unsubscribe from the Marathon Event Bus
    this.mebc.unsubscribe();
    setTimeout(function () {
        process.exit(0);
    }, 250);
};

// Return the health status
MarathonSlackBridge.prototype.getHealthStatus = function () {
    // Get health check status
    return this.healthCheckStatusCode;
};

MarathonSlackBridge.prototype.filterEventsByAppId = function (data) {
    let self = this;
    if (self.appIdRegExes.length === 0) {
        return true;
    } else {
        let useEvent = false;
        return self.appIdRegExes.some(function (regExp) {
            if (data.appId && regExp.test(data.appId.replace(/\//, ""))) {
                useEvent = true;
            } else if (data.currentStep && data.currentStep.actions){
                data.currentStep.actions.forEach(function(action){
                    if (regExp.test(action.app.replace(/\//, ""))) {
                        useEvent = true;
                    }
                });
            } else if (data.plan.steps && data.plan.steps.length > 0) {
                data.plan.steps.forEach(function(step){
                    if (step.actions && step.actions.length > 0){
                        step.actions.forEach(function(action){
                            if (regExp.test(action.app.replace(/\//, ""))) {
                                useEvent = true;
                            }
                        });
                    }
                });
            }
            return useEvent === true;
        });
    }
};

module.exports = MarathonSlackBridge;
<commit_msg>Bugfix for event filtering<commit_after>"use strict";

const EventEmitter = require("events").EventEmitter;
const util = require("util");

// Use the MarathonEventBusClient
const MarathonEventBusClient = require("marathon-event-bus-client");

// Use the SlackHandler
const SlackHandler = require("./SlackHandler");

/**
 * Represents a Marathon Slack Bridge
 * @constructor
 * @param {object} options - The option map object.
 */
function MarathonSlackBridge (options) {

    if (!(this instanceof MarathonSlackBridge)) {
        return new MarathonSlackBridge(options);
    }

    // Inherit from EventEmitter
    EventEmitter.call(this);

    let self = this;

    // Define Marathon options
    let marathonOptions = {
        marathonHost: options.marathonHost || process.env.MARATHON_HOST || "master.mesos",
        marathonPort: options.marathonPort || process.env.MARATHON_PORT || 8080,
        marathonProtocol: options.marathonProtocol || process.env.MARATHON_PROTOCOL || "http"
    };

    // Define event types
    self.eventTypes = [];

    // RegExes for filtering on appId
    self.appIdRegExes = [];

    // Overwrite default event types if defined via env var
    if (process.env.EVENT_TYPES) {
        // Use environment variable
        if (process.env.EVENT_TYPES.indexOf(",") > -1) {
            self.eventTypes = process.env.EVENT_TYPES.split(",");
        } else {
            self.eventTypes = [process.env.EVENT_TYPES];
        }
    } else {
        self.eventTypes = ["deployment_info", "deployment_success", "deployment_failed", "deployment_step_success", "deployment_step_failure", "group_change_success", "group_change_failed", "failed_health_check_event", "health_status_changed_event", "unhealthy_task_kill_event"]
    }

    // Set the event types for the marathonOptions
    marathonOptions.eventTypes = self.eventTypes;

    // Handle appId RegExes
    if (process.env.APP_ID_REGEXES) {
        // Use environment variable
        if (process.env.APP_ID_REGEXES.indexOf(",") > -1) {
            process.env.APP_ID_REGEXES.split(",").forEach(function (regExp) {
                try {
                    self.appIdRegExes.push(new RegExp(regExp.toString(), "i"));
                } catch(e) {
                    console.log("The provided RegExp " + regExp + " isn't a valid regular expression!");
                }
            });
        } else {
            try {
                self.appIdRegExes.push(new RegExp(regExp.toString(), "i"));
            } catch(e) {
                console.log("The provided RegExp " + regExp + " isn't a valid regular expression!");
            }
        }
    }

    // Health check status code
    self.healthCheckStatusCode = 503;

    // Instantiate SlackHandler
    self.slackHandler = new SlackHandler({
        slackWebHook: options.slackWebHook || process.env.SLACK_WEBHOOK_URL,
        slackChannel: options.slackChannel || process.env.SLACK_CHANNEL || "#marathon",
        slackBotName: options.slackBotName || process.env.SLACK_BOT_NAME || "Marathon Event Bot"
    });

    // Forward error events
    self.slackHandler.on("error", function(error) {
        self.emit("error", {
            timestamp: new Date().getTime(),
            message: "Error from SlackHandler: " + error
        });
    });

    // Forward sent_message events
    self.slackHandler.on("sent_message", function(message) {
        self.emit("sent_message", {
            timestamp: new Date().getTime(),
            message: message
        });
    });

    // Forward received_reply events
    self.slackHandler.on("received_reply", function(reply) {
        self.emit("received_reply", {
            timestamp: new Date().getTime(),
            message: reply
        });
    });

    marathonOptions.handlers = {};

    // Populate handler functions
    self.eventTypes.forEach(function (eventType) {
        // Set handler function
        marathonOptions.handlers[eventType] = function (type, data) {
            // Forward marathon_event events
            self.emit("marathon_event", {
                timestamp: new Date().getTime(),
                eventType: type,
                data: data
            });
            // Apply filter conditions
            if (self.filterEventsByAppId(data)) {
                // Render & send message
                self.slackHandler.sendMessage(self.slackHandler.renderMessage({ type: type, data: data }));
            }
        }
    });

    // Create MarathonEventBusClient instance
    self.mebc = new MarathonEventBusClient(marathonOptions);

    // Wait for "connected" event
    self.mebc.on("subscribed", function () {
        // Service is available
        self.healthCheckStatusCode = 200;
        // Emit subscribed event
        self.emit("subscribed", {
            timestamp: new Date().getTime(),
            message: "Subscribed to the Marathon Event Bus"
        });
    });

    // Wait for "unsubscribed" event
    self.mebc.on("unsubscribed", function () {
        // Service is unavailable
        self.healthCheckStatusCode = 503;
        // Forward unsubscribed event
        self.emit("unsubscribed", {
            timestamp: new Date().getTime(),
            message: "Unsubscribed from the Marathon Event Bus"
        });

    });

    // Catch error events
    self.mebc.on("error", function (errorObj) {
        // Forward error event
        self.emit("error", {
            timestamp: errorObj.timestamp,
            message: errorObj.error
        });
    });

}

// Inherit from EventEmitter
util.inherits(MarathonSlackBridge, EventEmitter);

// Wrapper for starting to listen to the Marathon Event Bus
MarathonSlackBridge.prototype.start = function () {
    // Subscribe to Marathon Event Bus
    this.mebc.subscribe();
};

// Wrapper for teardown
MarathonSlackBridge.prototype.stop = function () {
    // Unsubscribe from the Marathon Event Bus
    this.mebc.unsubscribe();
    setTimeout(function () {
        process.exit(0);
    }, 250);
};

// Return the health status
MarathonSlackBridge.prototype.getHealthStatus = function () {
    // Get health check status
    return this.healthCheckStatusCode;
};

MarathonSlackBridge.prototype.filterEventsByAppId = function (data) {
    let self = this;
    if (self.appIdRegExes.length === 0) {
        return true;
    } else {
        let useEvent = false;
        return self.appIdRegExes.some(function (regExp) {
            if (data.appId && regExp.test(data.appId.replace(/\//, ""))) {
                useEvent = true;
            } else if (data.currentStep && data.currentStep.actions){
                data.currentStep.actions.forEach(function(action){
                    if (regExp.test(action.app.replace(/\//, ""))) {
                        useEvent = true;
                    }
                });
            } else if (data.plan && data.plan.steps && data.plan.steps.length > 0) {
                data.plan.steps.forEach(function(step){
                    if (step.actions && step.actions.length > 0){
                        step.actions.forEach(function(action){
                            if (regExp.test(action.app.replace(/\//, ""))) {
                                useEvent = true;
                            }
                        });
                    }
                });
            }
            return useEvent === true;
        });
    }
};

module.exports = MarathonSlackBridge;
<|endoftext|>


<commit_before>'use strict';

/**
 *
 * @param {Object} jsonData
 * @param {string} jsonData[].Product
 * @param {string} xmlData
 * @param {string} requestUrl
 */
const icecat = function(jsonData, xmlData, requestUrl) {
  this.xmlData = xmlData;
  this.jsonData = jsonData;
  this.productData = jsonData['ICECAT-interface'].Product[0];
  this.requestUrl = requestUrl;
  this.returnCode = {
    FAIL: -1,
    SUCCESS: 1
  };
};

/**
 * getJSON
 * @returns {string}
 */
icecat.prototype.getJSON = function() {
  return this.jsonData;
};

/**
 * getXML
 * @returns {string}
 */
icecat.prototype.getXML = function() {
  return this.xmlData;
};

/**
 * getReturnCode
 * Example  returns -1 || 1
 * @returns {String}
 */
icecat.prototype.getReturnCode = function() {
  return parseInt(this.productData.$.Code, 10);
};

/**
 * getErrorMessage
 * @returns {string}
 */
icecat.prototype.getErrorMessage = function() {
  return this.productData.$.ErrorMessage;
};

/**
 * getName
 *
 * @returns Product Name {string}
 */
icecat.prototype.getName = function() {
  return this.productData.$.Name;
};

/**
 * getTitle
 *
 * @returns Product Title {string}
 */
icecat.prototype.getTitle = function() {
  return this.productData.$.Title;
};

/**
 * getReleaseDate
 *
 * @returns Product Release Date YYYY-MM-DD {string}
 */
icecat.prototype.getReleaseDate = function() {
  return this.productData.$.ReleaseDate;
};

/**
 * getLongDescription
 * @returns {string}
 */
icecat.prototype.getLongDescription = function() {
  try {
    return this.productData.ProductDescription[0].$.LongDesc;
  } catch (e) {
    return false;
  }
};

/**
 * getShortDescription
 * @returns {string}
 */
icecat.prototype.getShortDescription = function() {
  try {
    return this.productData.ProductDescription[0].$.ShortDesc;
  } catch (e) {
    return false;
  }
};

/**
 * getProductInfoPDFurl
 * @returns {string}
 */
icecat.prototype.getProductInfoPDFurl = function() {
  try {
    return this.productData.ProductDescription[0].$.PDFURL;
  } catch (e) {
    return false;
  }
};

/**
 * getProductManualPDFurl
 * @returns {string}
 */
icecat.prototype.getProductManualPDFurl = function() {
  try {
    return this.productData.ProductDescription[0].$.ManualPDFURL;
  } catch (e) {
    return false;
  }
};

/**
 * getProductUrl
 * @returns {string}
 */
icecat.prototype.getProductUrl = function() {
  try {
    return this.productData.ProductDescription[0].$.URL;
  } catch (e) {
    return false;
  }
};

/**
 * getSupplier
 * @returns {string}
 */
icecat.prototype.getSupplier = function() {
  return this.productData.Supplier[0].$.Name;
};

/**
 * getCategory
 * @returns {string}
 */
icecat.prototype.getCategory = function() {
  return this.productData.Category[0].Name[0].$.Value;
};

/**
 * getFamily
 * @returns {Object}
 */
icecat.prototype.getFamily = function() {
  try {
    return {
      id: this.productData.ProductFamily[0].Name[0].$.ID,
      name: this.productData.ProductFamily[0].Name[0].$.Value
    };
  } catch (e) {
    return false;
  }
};

/**
 * getID
 * @returns {string}
 */
icecat.prototype.getID = function() {
  return this.productData.$.ID;
};

/**
 * getEan
 * @returns {string}
 */
icecat.prototype.getEan = function() {
  return this.productData.EANCode[0].$.EAN;
};

/**
 * getImages
 * @returns {array}
 */
icecat.prototype.getImages = function() {
  if (!this.productData.ProductGallery.length || !this.productData.ProductGallery[0].ProductPicture.length) {
    return;
  }
  return this.productData.ProductGallery[0].ProductPicture.map((img) => {
    return {
      IsMain: img.$.IsMain,
      HighImg: img.$.Pic,
      LowImg: img.$.LowPic,
      ThumbImg: img.$.ThumbPic
    };
  });
};

/**
 * getSpecifications
 * @returns {array}
 */
icecat.prototype.getSpecifications = function() {
  return this.productData.ProductFeature.map((pSpec) => {
    const $ = pSpec.$;

    return {
      name: pSpec.Feature[0].Name[0].$.Value,
      presentationValue: $.Presentation_Value,
      value: $.Value,
      specId: $.CategoryFeature_ID,
      specGroupId: $.CategoryFeatureGroup_ID
    };
  });
};

/**
 * getMultimediaObjects
 * @returns {Array}
 */
icecat.prototype.getMultimediaObjects = function() {
  try {
    return this.productData.ProductMultimediaObject.map((mObj) => {
      const mmObj = mObj.MultimediaObject[0].$;
      return {
        contentType: mmObj.ContentType,
        thumbPic: mmObj.ThumbPic,
        description: mmObj.Description,
        keepAsURL: mmObj.KeepAsURL,
        size: mmObj.Size,
        url: mmObj.URL
      };
    });
  } catch (e) {
    return false;
  }
};

/**
 * getCategoryFeatureGroups
 * @returns {Array}
 */
icecat.prototype.getCategoryFeatureGroups = function() {
  try {
    return this.productData.CategoryFeatureGroup.map((cfgObj) => {
      const item = cfgObj.FeatureGroup[0].$;
      return {
        id: cfgObj.$.ID,
        name: cfgObj.FeatureGroup[0].Name[0].$.Value
      };
    });
  } catch (e) {
    return false;
  }
};

module.exports = icecat;
<commit_msg>Fix for issue #37 getImages() crash<commit_after>'use strict';

/**
 *
 * @param {Object} jsonData
 * @param {string} jsonData[].Product
 * @param {string} xmlData
 * @param {string} requestUrl
 */
const icecat = function(jsonData, xmlData, requestUrl) {
  this.xmlData = xmlData;
  this.jsonData = jsonData;
  this.productData = jsonData['ICECAT-interface'].Product[0];
  this.requestUrl = requestUrl;
  this.returnCode = {
    FAIL: -1,
    SUCCESS: 1
  };
};

/**
 * getJSON
 * @returns {string}
 */
icecat.prototype.getJSON = function() {
  return this.jsonData;
};

/**
 * getXML
 * @returns {string}
 */
icecat.prototype.getXML = function() {
  return this.xmlData;
};

/**
 * getReturnCode
 * Example  returns -1 || 1
 * @returns {String}
 */
icecat.prototype.getReturnCode = function() {
  return parseInt(this.productData.$.Code, 10);
};

/**
 * getErrorMessage
 * @returns {string}
 */
icecat.prototype.getErrorMessage = function() {
  return this.productData.$.ErrorMessage;
};

/**
 * getName
 *
 * @returns Product Name {string}
 */
icecat.prototype.getName = function() {
  return this.productData.$.Name;
};

/**
 * getTitle
 *
 * @returns Product Title {string}
 */
icecat.prototype.getTitle = function() {
  return this.productData.$.Title;
};

/**
 * getReleaseDate
 *
 * @returns Product Release Date YYYY-MM-DD {string}
 */
icecat.prototype.getReleaseDate = function() {
  return this.productData.$.ReleaseDate;
};

/**
 * getLongDescription
 * @returns {string}
 */
icecat.prototype.getLongDescription = function() {
  try {
    return this.productData.ProductDescription[0].$.LongDesc;
  } catch (e) {
    return false;
  }
};

/**
 * getShortDescription
 * @returns {string}
 */
icecat.prototype.getShortDescription = function() {
  try {
    return this.productData.ProductDescription[0].$.ShortDesc;
  } catch (e) {
    return false;
  }
};

/**
 * getProductInfoPDFurl
 * @returns {string}
 */
icecat.prototype.getProductInfoPDFurl = function() {
  try {
    return this.productData.ProductDescription[0].$.PDFURL;
  } catch (e) {
    return false;
  }
};

/**
 * getProductManualPDFurl
 * @returns {string}
 */
icecat.prototype.getProductManualPDFurl = function() {
  try {
    return this.productData.ProductDescription[0].$.ManualPDFURL;
  } catch (e) {
    return false;
  }
};

/**
 * getProductUrl
 * @returns {string}
 */
icecat.prototype.getProductUrl = function() {
  try {
    return this.productData.ProductDescription[0].$.URL;
  } catch (e) {
    return false;
  }
};

/**
 * getSupplier
 * @returns {string}
 */
icecat.prototype.getSupplier = function() {
  return this.productData.Supplier[0].$.Name;
};

/**
 * getCategory
 * @returns {string}
 */
icecat.prototype.getCategory = function() {
  return this.productData.Category[0].Name[0].$.Value;
};

/**
 * getFamily
 * @returns {Object}
 */
icecat.prototype.getFamily = function() {
  try {
    return {
      id: this.productData.ProductFamily[0].Name[0].$.ID,
      name: this.productData.ProductFamily[0].Name[0].$.Value
    };
  } catch (e) {
    return false;
  }
};

/**
 * getID
 * @returns {string}
 */
icecat.prototype.getID = function() {
  return this.productData.$.ID;
};

/**
 * getEan
 * @returns {string}
 */
icecat.prototype.getEan = function() {
  return this.productData.EANCode[0].$.EAN;
};

/**
 * getImages
 * @returns {array}
 */
icecat.prototype.getImages = function() {
  this.productData.ProductGallery = this.productData.ProductGallery || [];
  if (!this.productData.ProductGallery.length || !this.productData.ProductGallery[0].ProductPicture.length) {
    return;
  }
  return this.productData.ProductGallery[0].ProductPicture.map((img) => {
    return {
      IsMain: img.$.IsMain,
      HighImg: img.$.Pic,
      LowImg: img.$.LowPic,
      ThumbImg: img.$.ThumbPic
    };
  });
};

/**
 * getSpecifications
 * @returns {array}
 */
icecat.prototype.getSpecifications = function() {
  return this.productData.ProductFeature.map((pSpec) => {
    const $ = pSpec.$;

    return {
      name: pSpec.Feature[0].Name[0].$.Value,
      presentationValue: $.Presentation_Value,
      value: $.Value,
      specId: $.CategoryFeature_ID,
      specGroupId: $.CategoryFeatureGroup_ID
    };
  });
};

/**
 * getMultimediaObjects
 * @returns {Array}
 */
icecat.prototype.getMultimediaObjects = function() {
  try {
    return this.productData.ProductMultimediaObject.map((mObj) => {
      const mmObj = mObj.MultimediaObject[0].$;
      return {
        contentType: mmObj.ContentType,
        thumbPic: mmObj.ThumbPic,
        description: mmObj.Description,
        keepAsURL: mmObj.KeepAsURL,
        size: mmObj.Size,
        url: mmObj.URL
      };
    });
  } catch (e) {
    return false;
  }
};

/**
 * getCategoryFeatureGroups
 * @returns {Array}
 */
icecat.prototype.getCategoryFeatureGroups = function() {
  try {
    return this.productData.CategoryFeatureGroup.map((cfgObj) => {
      const item = cfgObj.FeatureGroup[0].$;
      return {
        id: cfgObj.$.ID,
        name: cfgObj.FeatureGroup[0].Name[0].$.Value
      };
    });
  } catch (e) {
    return false;
  }
};

module.exports = icecat;
<|endoftext|>