<commit_before>import fs from 'fs-extra'
import os from 'os'
import path from 'path'
import readdirp from 'readdirp'
import url from 'url'
// TODO: Remove cozy-device-sdk dependency
import { device } from 'cozy-device-sdk'
import fetch from 'node-fetch'
import printit from 'printit'
let log = printit({
  prefix: 'Cozy Desktop  ',
  date: true
})

import { Console } from 'console'
import { EventEmitter } from 'events'

import Config from './config'
import Pouch from './pouch'
import Ignore from './ignore'
import Merge from './merge'
import Prep from './prep'
import Local from './local'
import Remote from './remote'
import Sync from './sync'

// TODO: App.Permissions v3
// eslint-disable-next-line no-unused-vars
let Permissions = {
  'File': {
    'description': 'Useful to synchronize your files'
  },
  'Folder': {
    'description': 'Useful to synchronize your folders'
  },
  'Binary': {
    'description': 'Useful to synchronize the content of your files'
  },
  'send mail from user': {
    'description': 'Useful to send issues by mail to the cozy team'
  }
}

// App is the entry point for the CLI and GUI.
// They both can do actions and be notified by events via an App instance.
let MAX_LOG_SIZE
class App {
  static initClass () {
    // When a log file weights more than 0.5Mo, rotate it
    MAX_LOG_SIZE = 500000
  }

  // basePath is the directory where the config and pouch are saved
  constructor (basePath, fetch_ = fetch) {
    this.lang = 'fr'
    if (basePath == null) { basePath = os.homedir() }
    basePath = path.resolve(basePath)
    this.basePath = path.join(basePath, '.cozy-desktop')
    this.config = new Config(this.basePath)
    this.pouch = new Pouch(this.config)
    this.events = new EventEmitter()
    this.fetch = fetch_
  }

  // This method is here to be surcharged by the UI
  // to ask its password to the user
  //
  // callback is a function that takes two parameters: error and password
  askPassword (callback) {
    callback(new Error('Not implemented'), null)
  }

  // This method is here to be surcharged by the UI
  // to ask for a confirmation before doing something that can't be cancelled
  //
  // callback is a function that takes two parameters: error and a boolean
  askConfirmation (callback) {
    callback(new Error('Not implemented'), null)
  }

  // Configure a file to write logs to
  writeLogsTo (logfile) {
    this.logfile = logfile
    this.writeToLogfile()
    if (this.logsInterval) { clearInterval(this.logsInterval) }
    this.logsInterval = setInterval(this.rotateLogfile, 10000)
  }

  // Write logs in a file, by overriding the global console
  writeToLogfile () {
    let out = fs.createWriteStream(this.logfile, {flags: 'a+', mode: 0o0644})
    printit.console = new Console(out, out)
  }

  // Rotate the log file if it's too heavy
  rotateLogfile () {
    fs.stat(this.logfile, (err, stats) => {
      if (err || (stats.size < MAX_LOG_SIZE)) { return }
      fs.rename(this.logfile, `${this.logfile}.old`, this.writeToLogfile)
    })
  }

  // Parse the URL
  parseCozyUrl (cozyUrl) {
    if (cozyUrl.indexOf(':') === -1) {
      if (cozyUrl.indexOf('.') === -1) {
        cozyUrl += '.cozycloud.cc'
      }
      cozyUrl = `https://${cozyUrl}`
    }
    return url.parse(cozyUrl)
  }

  // Check that the URL belongs to a cozy
  async pingCozy (cozyUrl) {
    let parsed = this.parseCozyUrl(cozyUrl)
    if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
      let err = new Error('Your URL looks invalid')
      log.warn(err)
      return Promise.reject(err)
    }
    cozyUrl = url.format(parsed)

    let resp, body
    resp = await this.fetch(cozyUrl + 'status')

    if (resp.status !== 200) {
      throw new Error(`Unexpected response status code: ${resp.status}`)
    }

    body = await resp.json()
    let dumpBody = () => JSON.stringify(body)

    switch (body.message) {
      case 'OK':
        return cozyUrl
      case 'KO':
        throw new Error(`Cozy is KO: ${dumpBody()}`)
      default:
        throw new Error(`Cannot extract message: ${dumpBody()}`)
    }
  }

  // Register a device on the remote cozy
  registerRemote (cozyUrl, deviceName, callback) {
    let parsed = this.parseCozyUrl(cozyUrl)
    cozyUrl = url.format(parsed)
    if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
      let err = new Error(`Your URL looks invalid: ${cozyUrl}`)
      log.warn(err)
      callback(err)
      return
    }
    if (deviceName == null) { deviceName = os.hostname() || 'desktop' }
    this.askPassword(function (_, password) {
      // TODO: App.registerRemote() v3
      callback(null, {deviceName, password})
    })
  }

  // Save the config with all the informations for synchonization
  saveConfig (cozyUrl, syncPath, deviceName, password, callback) {
    fs.ensureDir(syncPath, err => {
      if (err) {
        callback(err)
      } else {
        let options = {
          path: path.resolve(syncPath),
          url: cozyUrl,
          deviceName,
          password
        }
        this.config.addRemoteCozy(options)
        log.info('The remote Cozy has properly been configured ' +
                 'to work with current device.')
        callback(null)
      }
    })
  }

  // Register current device to remote Cozy and then save related informations
  // to the config file (used by CLI, not GUI)
  addRemote (cozyUrl, syncPath, deviceName, callback) {
    this.registerRemote(cozyUrl, deviceName, (err, credentials) => {
      if (err) {
        log.error('An error occured while registering your device.')
        let parsed = this.parseCozyUrl(cozyUrl)
        if (err.code === 'ENOTFOUND') {
          log.warn(`The DNS resolution for ${parsed.hostname} failed.`)
          log.warn('Are you sure the domain is OK?')
        } else if (err === 'Bad credentials') {
          log.warn(err)
          log.warn('Are you sure there are no typo on the password?')
        } else {
          log.error(err)
          if (parsed.protocol === 'http:') {
            log.warn('Did you try with an httpS URL?')
          }
        }
        __guardFunc__(callback, f => f(err))
      } else {
        ({ deviceName } = credentials)
        let { password } = credentials
        log.info(`Device ${deviceName} has been added to ${cozyUrl}`)
        this.saveConfig(cozyUrl, syncPath, deviceName, password, err => __guardFunc__(callback, f1 => f1(err, credentials)))
      }
    })
  }

  // Unregister current device from remote Cozy and then remove remote from
  // the config file
  removeRemote (deviceName, callback) {
    if (callback == null) { callback = function () {} }
    let conf = this.config.getDevice()
    let cozyUrl = conf.url
    let { password } = conf
    device.unregisterDevice(cozyUrl, deviceName, password, err => {
      if (err && (err.message !== 'Request unauthorized')) {
        log.error('An error occured while unregistering your device.')
        log.error(err)
        callback(err)
      } else {
        log.info('Current device properly removed from remote cozy.')
        fs.remove(this.basePath, callback)
      }
    })
  }

  // Send an issue by mail to the support
  sendMailToSupport (content, callback) {
    let conf = this.config.getDevice()
    let cozyUrl = conf.url
    let { deviceName } = conf
    let { password } = conf
    let mail = {
      to: 'log-desktop@cozycloud.cc',
      subject: 'Ask support for cozy-desktop',
      content
    }
    if (this.logfile) {
      let attachment = {
        content: fs.readFileSync(this.logfile, 'utf-8'),
        filename: path.basename(this.logfile),
        contentType: 'application/text'
      }
      mail.attachments = [attachment]
    }
    device.sendMailFromUser(cozyUrl, deviceName, password, mail, callback)
  }

  // Load ignore rules
  loadIgnore () {
    let ignored
    try {
      let syncPath = this.config.getDevice().path
      ignored = fs.readFileSync(path.join(syncPath, '.cozyignore'))
      ignored = ignored.toString().split('\n')
    } catch (error) {
      ignored = []
    }
    this.ignore = new Ignore(ignored).addDefaultRules()
  }

  // Instanciate some objects before sync
  instanciate () {
    this.loadIgnore()
    this.merge = new Merge(this.pouch)
    this.prep = new Prep(this.merge, this.ignore)
    this.local = this.merge.local = new Local(this.config, this.prep, this.pouch, this.events)
    this.remote = this.merge.remote = new Remote(this.config, this.prep, this.pouch)
    this.sync = new Sync(this.pouch, this.local, this.remote, this.ignore, this.events)
    this.sync.getDiskSpace = this.getDiskSpace
  }

  // Start the synchronization
  startSync (mode, callback) {
    this.config.setMode(mode)
    log.info('Run first synchronisation...')
    this.sync.start(mode, err => {
      this.sync.stop(function () {})
      if (err) {
        log.error(err)
        if (err.stack) { log.error(err.stack) }
      }
      __guardFunc__(callback, f => f(err))
    })
  }

  // Stop the synchronisation
  stopSync (callback) {
    if (callback == null) { callback = function () {} }
    if (this.sync) {
      this.sync.stop(callback)
    } else {
      callback()
    }
  }

  // Start database sync process and setup file change watcher
  synchronize (mode, callback) {
    let conf = this.config.getDevice()
    if ((conf.deviceName != null) && (conf.url != null) && (conf.path != null)) {
      this.instanciate()
      this.startSync(mode, callback)
    } else {
      log.error('No configuration found, please run add-remote-cozy' +
                'command before running a synchronization.')
      __guardFunc__(callback, f => f(new Error('No config')))
    }
  }

  // Display a list of watchers for debugging purpose
  debugWatchers () {
    __guard__(this.local, x => x.watcher.debug())
  }

  // Call the callback for each file
  walkFiles (args, callback) {
    this.loadIgnore()
    let options = {
      root: this.config.getDevice().path,
      directoryFilter: '!.cozy-desktop',
      entryType: 'both'
    }
    readdirp(options)
      .on('warn', err => log.warn(err))
      .on('error', err => log.error(err))
      .on('data', data => {
        let doc = {
          _id: data.path,
          docType: data.stat.isFile() ? 'file' : 'folder'
        }
        if (this.ignore.isIgnored(doc) === (args.ignored != null)) {
          callback(data.path)
        }
      })
  }

  // Recreate the local pouch database
  resetDatabase (callback) {
    this.askConfirmation((err, ok) => {
      if (err) {
        log.error(err)
      } else if (ok) {
        log.info('Recreates the local database...')
        this.pouch.resetDatabase(function () {
          log.info('Database recreated')
          __guardFunc__(callback, f => f())
        })
      } else {
        log.info('Abort!')
      }
    })
  }

  // Return the whole content of the database
  allDocs (callback) {
    this.pouch.db.allDocs({include_docs: true}, callback)
  }

  // Return all docs for a given query
  query (query, callback) {
    this.pouch.db.query(query, {include_docs: true}, callback)
  }

  // Get disk space informations from the cozy
  getDiskSpace (callback) {
    // TODO: App.getDiskSpace() v3
    callback(null, {})
  }
}
App.initClass()

export default App

function __guardFunc__ (func, transform) {
  return typeof func === 'function' ? transform(func) : undefined
}
function __guard__ (value, transform) {
  return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined
}
<commit_msg>app: Temporarily force pull mode only<commit_after>import fs from 'fs-extra'
import os from 'os'
import path from 'path'
import readdirp from 'readdirp'
import url from 'url'
// TODO: Remove cozy-device-sdk dependency
import { device } from 'cozy-device-sdk'
import fetch from 'node-fetch'
import printit from 'printit'
let log = printit({
  prefix: 'Cozy Desktop  ',
  date: true
})

import { Console } from 'console'
import { EventEmitter } from 'events'

import Config from './config'
import Pouch from './pouch'
import Ignore from './ignore'
import Merge from './merge'
import Prep from './prep'
import Local from './local'
import Remote from './remote'
import Sync from './sync'

// TODO: App.Permissions v3
// eslint-disable-next-line no-unused-vars
let Permissions = {
  'File': {
    'description': 'Useful to synchronize your files'
  },
  'Folder': {
    'description': 'Useful to synchronize your folders'
  },
  'Binary': {
    'description': 'Useful to synchronize the content of your files'
  },
  'send mail from user': {
    'description': 'Useful to send issues by mail to the cozy team'
  }
}

// App is the entry point for the CLI and GUI.
// They both can do actions and be notified by events via an App instance.
let MAX_LOG_SIZE
class App {
  static initClass () {
    // When a log file weights more than 0.5Mo, rotate it
    MAX_LOG_SIZE = 500000
  }

  // basePath is the directory where the config and pouch are saved
  constructor (basePath, fetch_ = fetch) {
    this.lang = 'fr'
    if (basePath == null) { basePath = os.homedir() }
    basePath = path.resolve(basePath)
    this.basePath = path.join(basePath, '.cozy-desktop')
    this.config = new Config(this.basePath)
    this.pouch = new Pouch(this.config)
    this.events = new EventEmitter()
    this.fetch = fetch_
  }

  // This method is here to be surcharged by the UI
  // to ask its password to the user
  //
  // callback is a function that takes two parameters: error and password
  askPassword (callback) {
    callback(new Error('Not implemented'), null)
  }

  // This method is here to be surcharged by the UI
  // to ask for a confirmation before doing something that can't be cancelled
  //
  // callback is a function that takes two parameters: error and a boolean
  askConfirmation (callback) {
    callback(new Error('Not implemented'), null)
  }

  // Configure a file to write logs to
  writeLogsTo (logfile) {
    this.logfile = logfile
    this.writeToLogfile()
    if (this.logsInterval) { clearInterval(this.logsInterval) }
    this.logsInterval = setInterval(this.rotateLogfile, 10000)
  }

  // Write logs in a file, by overriding the global console
  writeToLogfile () {
    let out = fs.createWriteStream(this.logfile, {flags: 'a+', mode: 0o0644})
    printit.console = new Console(out, out)
  }

  // Rotate the log file if it's too heavy
  rotateLogfile () {
    fs.stat(this.logfile, (err, stats) => {
      if (err || (stats.size < MAX_LOG_SIZE)) { return }
      fs.rename(this.logfile, `${this.logfile}.old`, this.writeToLogfile)
    })
  }

  // Parse the URL
  parseCozyUrl (cozyUrl) {
    if (cozyUrl.indexOf(':') === -1) {
      if (cozyUrl.indexOf('.') === -1) {
        cozyUrl += '.cozycloud.cc'
      }
      cozyUrl = `https://${cozyUrl}`
    }
    return url.parse(cozyUrl)
  }

  // Check that the URL belongs to a cozy
  async pingCozy (cozyUrl) {
    let parsed = this.parseCozyUrl(cozyUrl)
    if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
      let err = new Error('Your URL looks invalid')
      log.warn(err)
      return Promise.reject(err)
    }
    cozyUrl = url.format(parsed)

    let resp, body
    resp = await this.fetch(cozyUrl + 'status')

    if (resp.status !== 200) {
      throw new Error(`Unexpected response status code: ${resp.status}`)
    }

    body = await resp.json()
    let dumpBody = () => JSON.stringify(body)

    switch (body.message) {
      case 'OK':
        return cozyUrl
      case 'KO':
        throw new Error(`Cozy is KO: ${dumpBody()}`)
      default:
        throw new Error(`Cannot extract message: ${dumpBody()}`)
    }
  }

  // Register a device on the remote cozy
  registerRemote (cozyUrl, deviceName, callback) {
    let parsed = this.parseCozyUrl(cozyUrl)
    cozyUrl = url.format(parsed)
    if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
      let err = new Error(`Your URL looks invalid: ${cozyUrl}`)
      log.warn(err)
      callback(err)
      return
    }
    if (deviceName == null) { deviceName = os.hostname() || 'desktop' }
    this.askPassword(function (_, password) {
      // TODO: App.registerRemote() v3
      callback(null, {deviceName, password})
    })
  }

  // Save the config with all the informations for synchonization
  saveConfig (cozyUrl, syncPath, deviceName, password, callback) {
    fs.ensureDir(syncPath, err => {
      if (err) {
        callback(err)
      } else {
        let options = {
          path: path.resolve(syncPath),
          url: cozyUrl,
          deviceName,
          password
        }
        this.config.addRemoteCozy(options)
        log.info('The remote Cozy has properly been configured ' +
                 'to work with current device.')
        callback(null)
      }
    })
  }

  // Register current device to remote Cozy and then save related informations
  // to the config file (used by CLI, not GUI)
  addRemote (cozyUrl, syncPath, deviceName, callback) {
    this.registerRemote(cozyUrl, deviceName, (err, credentials) => {
      if (err) {
        log.error('An error occured while registering your device.')
        let parsed = this.parseCozyUrl(cozyUrl)
        if (err.code === 'ENOTFOUND') {
          log.warn(`The DNS resolution for ${parsed.hostname} failed.`)
          log.warn('Are you sure the domain is OK?')
        } else if (err === 'Bad credentials') {
          log.warn(err)
          log.warn('Are you sure there are no typo on the password?')
        } else {
          log.error(err)
          if (parsed.protocol === 'http:') {
            log.warn('Did you try with an httpS URL?')
          }
        }
        __guardFunc__(callback, f => f(err))
      } else {
        ({ deviceName } = credentials)
        let { password } = credentials
        log.info(`Device ${deviceName} has been added to ${cozyUrl}`)
        this.saveConfig(cozyUrl, syncPath, deviceName, password, err => __guardFunc__(callback, f1 => f1(err, credentials)))
      }
    })
  }

  // Unregister current device from remote Cozy and then remove remote from
  // the config file
  removeRemote (deviceName, callback) {
    if (callback == null) { callback = function () {} }
    let conf = this.config.getDevice()
    let cozyUrl = conf.url
    let { password } = conf
    device.unregisterDevice(cozyUrl, deviceName, password, err => {
      if (err && (err.message !== 'Request unauthorized')) {
        log.error('An error occured while unregistering your device.')
        log.error(err)
        callback(err)
      } else {
        log.info('Current device properly removed from remote cozy.')
        fs.remove(this.basePath, callback)
      }
    })
  }

  // Send an issue by mail to the support
  sendMailToSupport (content, callback) {
    let conf = this.config.getDevice()
    let cozyUrl = conf.url
    let { deviceName } = conf
    let { password } = conf
    let mail = {
      to: 'log-desktop@cozycloud.cc',
      subject: 'Ask support for cozy-desktop',
      content
    }
    if (this.logfile) {
      let attachment = {
        content: fs.readFileSync(this.logfile, 'utf-8'),
        filename: path.basename(this.logfile),
        contentType: 'application/text'
      }
      mail.attachments = [attachment]
    }
    device.sendMailFromUser(cozyUrl, deviceName, password, mail, callback)
  }

  // Load ignore rules
  loadIgnore () {
    let ignored
    try {
      let syncPath = this.config.getDevice().path
      ignored = fs.readFileSync(path.join(syncPath, '.cozyignore'))
      ignored = ignored.toString().split('\n')
    } catch (error) {
      ignored = []
    }
    this.ignore = new Ignore(ignored).addDefaultRules()
  }

  // Instanciate some objects before sync
  instanciate () {
    this.loadIgnore()
    this.merge = new Merge(this.pouch)
    this.prep = new Prep(this.merge, this.ignore)
    this.local = this.merge.local = new Local(this.config, this.prep, this.pouch, this.events)
    this.remote = this.merge.remote = new Remote(this.config, this.prep, this.pouch)
    this.sync = new Sync(this.pouch, this.local, this.remote, this.ignore, this.events)
    this.sync.getDiskSpace = this.getDiskSpace
  }

  // Start the synchronization
  startSync (mode, callback) {
    // FIXME: v3: Temporarily force pull mode only, until push works
    mode = 'pull'

    this.config.setMode(mode)
    log.info('Run first synchronisation...')
    this.sync.start(mode, err => {
      this.sync.stop(function () {})
      if (err) {
        log.error(err)
        if (err.stack) { log.error(err.stack) }
      }
      __guardFunc__(callback, f => f(err))
    })
  }

  // Stop the synchronisation
  stopSync (callback) {
    if (callback == null) { callback = function () {} }
    if (this.sync) {
      this.sync.stop(callback)
    } else {
      callback()
    }
  }

  // Start database sync process and setup file change watcher
  synchronize (mode, callback) {
    let conf = this.config.getDevice()
    if ((conf.deviceName != null) && (conf.url != null) && (conf.path != null)) {
      this.instanciate()
      this.startSync(mode, callback)
    } else {
      log.error('No configuration found, please run add-remote-cozy' +
                'command before running a synchronization.')
      __guardFunc__(callback, f => f(new Error('No config')))
    }
  }

  // Display a list of watchers for debugging purpose
  debugWatchers () {
    __guard__(this.local, x => x.watcher.debug())
  }

  // Call the callback for each file
  walkFiles (args, callback) {
    this.loadIgnore()
    let options = {
      root: this.config.getDevice().path,
      directoryFilter: '!.cozy-desktop',
      entryType: 'both'
    }
    readdirp(options)
      .on('warn', err => log.warn(err))
      .on('error', err => log.error(err))
      .on('data', data => {
        let doc = {
          _id: data.path,
          docType: data.stat.isFile() ? 'file' : 'folder'
        }
        if (this.ignore.isIgnored(doc) === (args.ignored != null)) {
          callback(data.path)
        }
      })
  }

  // Recreate the local pouch database
  resetDatabase (callback) {
    this.askConfirmation((err, ok) => {
      if (err) {
        log.error(err)
      } else if (ok) {
        log.info('Recreates the local database...')
        this.pouch.resetDatabase(function () {
          log.info('Database recreated')
          __guardFunc__(callback, f => f())
        })
      } else {
        log.info('Abort!')
      }
    })
  }

  // Return the whole content of the database
  allDocs (callback) {
    this.pouch.db.allDocs({include_docs: true}, callback)
  }

  // Return all docs for a given query
  query (query, callback) {
    this.pouch.db.query(query, {include_docs: true}, callback)
  }

  // Get disk space informations from the cozy
  getDiskSpace (callback) {
    // TODO: App.getDiskSpace() v3
    callback(null, {})
  }
}
App.initClass()

export default App

function __guardFunc__ (func, transform) {
  return typeof func === 'function' ? transform(func) : undefined
}
function __guard__ (value, transform) {
  return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined
}
<|endoftext|>


<commit_before>import DS from 'ember-data';
const { Model, attr, belongsTo } = DS;

export default Model.extend({
  amount: attr('number'),
  order: belongsTo('order'),
  paymentMethod: belongsTo('payment-method'),
  token: attr('string'),
  cardNumber: attr('string'),
  cardholder: attr('string'),
  validFrom: attr('string'),
  expiryDate: attr('string'),
  issueNumber: attr('string'),
  cvv: attr('string')
});
<commit_msg>Add cardType to payment model<commit_after>import DS from 'ember-data';
const { Model, attr, belongsTo } = DS;

export default Model.extend({
  amount: attr('number'),
  order: belongsTo('order'),
  paymentMethod: belongsTo('payment-method'),
  token: attr('string'),
  cardNumber: attr('string'),
  cardholder: attr('string'),
  cardType: attr('string'),
  validFrom: attr('string'),
  expiryDate: attr('string'),
  issueNumber: attr('string'),
  cvv: attr('string')
});
<|endoftext|>


<commit_before>/* global define */
(function() {
    'use strict';
    var osdConfigKeys = [
            'noin',
            'noout',
            'noup',
            'nodown',
            'pause',
            'noscrub',
            'nodeep-scrub',
            'nobackfill',
            'norecover'
    ];
    var SPINNER_ICON = '';
    var CHECK_CIRCLE_ICON = '';

    define(['lodash', 'helpers/server-helpers', 'helpers/cluster-settings-helpers', 'helpers/cluster-response-helpers'], function(_, serverHelpers, clusterSettingsHelpers, responseHelpers) {

        var RootController = function($q, $log, $timeout, $rootScope, $location, $scope, KeyService, ClusterService, ToolService, ServerService, $modal, OSDConfigService, RequestTrackingService, config) {
            if (ClusterService.id === null) {
                $location.path(config.getFirstViewPath());
                return;
            }

            $scope.clusterName = ClusterService.clusterModel.name;

            var server = serverHelpers.makeFunctions($scope, $rootScope, $log, $timeout, ServerService, KeyService, $modal);
            $scope.acceptMinion = server.acceptMinion;
            $scope.detailView = function(id) {
                var modal = $modal({
                    title: id,
                    template: 'views/detail-grains-modal.html',
                    show: true
                });
                server.detailView(id).then(function(pairs) {
                    modal.$scope.pairs = pairs;
                });
            };

            function refreshKeys() {
                $log.debug('refreshing keys');
                KeyService.getList().then(server.processMinionChanges).then(function(all) {
                    $scope.cols = all.accepted;
                    $scope.pcols = all.pre;
                    $scope.hidePre = all.hidePre;
                });
                $rootScope.keyTimer = $timeout(refreshKeys, config.getPollTimeoutMs());
            }

            function approveAll() {
                var minions = _.flatten($scope.pcols);
                $scope.approveAllDisabled = true;
                minions = _.map(minions, function(minion) {
                    minion.label = SPINNER_ICON;
                    minion.disabled = true;
                    return minion.id;
                });
                var start = Date.now();
                KeyService.accept(minions).then(function( /*resp*/ ) {
                    var elapsed = Date.now() - start;
                    var timeout = elapsed < 1000 ? 1000 - elapsed : 0;
                    $timeout(function() {
                        minions = _.each(_.flatten($scope.pcols), function(minion) {
                            minion.label = CHECK_CIRCLE_ICON;
                        });
                    }, timeout);
                }, function(error) {
                    /* TODO pop a modal or use an interceptor */
                    $log.error(error);
                });
            }

            $scope.approveAll = approveAll;

            var response = responseHelpers.makeFunctions($q, $timeout, osdConfigKeys);
            var breadcrumbs = response.makeBreadcrumbs($scope.clusterName);
            $scope.breadcrumbs = breadcrumbs.servers;

            clusterSettingsHelpers.makeFunctions($log, $scope, $timeout, $q, breadcrumbs, OSDConfigService, $modal, osdConfigKeys, RequestTrackingService).initialize().then(function(cluster) {
                $scope.helpInfo = cluster.helpInfo;
                $scope.reset = cluster.reset;
                $scope.updateSettings = cluster.updateSettings;
            });

            var promises = [KeyService.getList(), ToolService.config(), OSDConfigService.get()];
            var start = Date.now();
            $q.all(promises).then(function(results) {
                $rootScope.keyTimer = $timeout(refreshKeys, config.getPollTimeoutMs());
                $scope.up = true;
                var elapsed = Date.now() - start;
                var timeout = elapsed < 600 ? 600 - elapsed : 0;
                $scope.hidePre = true;
                $timeout(function() {
                    var minions = _.reduce(results[0], function(accumulator, minion) {
                        if (minion.status === 'pre') {
                            accumulator.pre.push(minion);
                        } else {
                            accumulator.accept.push(minion);
                        }
                        return accumulator;
                    }, {
                        accept: [],
                        pre: []
                    });
                    $scope.pcols = response.bucketMinions(minions.pre);
                    $scope.cols = response.bucketMinions(minions.accept);
                    $scope.hidePre = _.flatten(minions.pre).length === 0;
                }, timeout);
                response.processConfigs(results[1]).then(function(configs) {
                    $scope.configs = configs;
                });
                response.osdConfigsInit(results[2]).then(function(osdConfigs) {
                    $scope.osdconfigs = osdConfigs;
                    $scope.osdconfigsdefaults = angular.copy(osdConfigs);
                });
            });
        };
        return ['$q', '$log', '$timeout', '$rootScope', '$location', '$scope', 'KeyService', 'ClusterService', 'ToolService', 'ServerService', '$modal', 'OSDConfigService', 'RequestTrackingService', 'ConfigurationService', RootController];
    });
})();
<commit_msg>Add modal helper on error to error path<commit_after>/* global define */
(function() {
    'use strict';
    var osdConfigKeys = [
            'noin',
            'noout',
            'noup',
            'nodown',
            'pause',
            'noscrub',
            'nodeep-scrub',
            'nobackfill',
            'norecover'
    ];
    var SPINNER_ICON = '';
    var CHECK_CIRCLE_ICON = '';

    define(['lodash', 'helpers/server-helpers', 'helpers/cluster-settings-helpers', 'helpers/cluster-response-helpers', 'helpers/modal-helpers'], function(_, serverHelpers, clusterSettingsHelpers, responseHelpers, modalHelpers) {

        var RootController = function($q, $log, $timeout, $rootScope, $location, $scope, KeyService, ClusterService, ToolService, ServerService, $modal, OSDConfigService, RequestTrackingService, config) {
            if (ClusterService.id === null) {
                $location.path(config.getFirstViewPath());
                return;
            }

            $scope.clusterName = ClusterService.clusterModel.name;

            var server = serverHelpers.makeFunctions($scope, $rootScope, $log, $timeout, ServerService, KeyService, $modal);
            $scope.acceptMinion = server.acceptMinion;
            $scope.detailView = function(id) {
                var modal = $modal({
                    title: id,
                    template: 'views/detail-grains-modal.html',
                    show: true
                });
                server.detailView(id).then(function(pairs) {
                    modal.$scope.pairs = pairs;
                });
            };

            function refreshKeys() {
                $log.debug('refreshing keys');
                KeyService.getList().then(server.processMinionChanges).then(function(all) {
                    $scope.cols = all.accepted;
                    $scope.pcols = all.pre;
                    $scope.hidePre = all.hidePre;
                });
                $rootScope.keyTimer = $timeout(refreshKeys, config.getPollTimeoutMs());
            }

            function approveAll() {
                var minions = _.flatten($scope.pcols);
                $scope.approveAllDisabled = true;
                minions = _.map(minions, function(minion) {
                    minion.label = SPINNER_ICON;
                    minion.disabled = true;
                    return minion.id;
                });
                var start = Date.now();
                KeyService.accept(minions).then(function( /*resp*/ ) {
                    var elapsed = Date.now() - start;
                    var timeout = elapsed < 1000 ? 1000 - elapsed : 0;
                    $timeout(function() {
                        minions = _.each(_.flatten($scope.pcols), function(minion) {
                            minion.label = CHECK_CIRCLE_ICON;
                        });
                    }, timeout);
                }, modalHelpers.makeOnError($modal({
                    show: true,
                    html: true,
                    template: 'views/custom-modal.html',
                    backdrop: 'static'
                })));
            }

            $scope.approveAll = approveAll;

            var response = responseHelpers.makeFunctions($q, $timeout, osdConfigKeys);
            var breadcrumbs = response.makeBreadcrumbs($scope.clusterName);
            $scope.breadcrumbs = breadcrumbs.servers;

            clusterSettingsHelpers.makeFunctions($log, $scope, $timeout, $q, breadcrumbs, OSDConfigService, $modal, osdConfigKeys, RequestTrackingService).initialize().then(function(cluster) {
                $scope.helpInfo = cluster.helpInfo;
                $scope.reset = cluster.reset;
                $scope.updateSettings = cluster.updateSettings;
            });

            var promises = [KeyService.getList(), ToolService.config(), OSDConfigService.get()];
            var start = Date.now();
            $q.all(promises).then(function(results) {
                $rootScope.keyTimer = $timeout(refreshKeys, config.getPollTimeoutMs());
                $scope.up = true;
                var elapsed = Date.now() - start;
                var timeout = elapsed < 600 ? 600 - elapsed : 0;
                $scope.hidePre = true;
                $timeout(function() {
                    var minions = _.reduce(results[0], function(accumulator, minion) {
                        if (minion.status === 'pre') {
                            accumulator.pre.push(minion);
                        } else {
                            accumulator.accept.push(minion);
                        }
                        return accumulator;
                    }, {
                        accept: [],
                        pre: []
                    });
                    $scope.pcols = response.bucketMinions(minions.pre);
                    $scope.cols = response.bucketMinions(minions.accept);
                    $scope.hidePre = _.flatten(minions.pre).length === 0;
                }, timeout);
                response.processConfigs(results[1]).then(function(configs) {
                    $scope.configs = configs;
                });
                response.osdConfigsInit(results[2]).then(function(osdConfigs) {
                    $scope.osdconfigs = osdConfigs;
                    $scope.osdconfigsdefaults = angular.copy(osdConfigs);
                });
            });
        };
        return ['$q', '$log', '$timeout', '$rootScope', '$location', '$scope', 'KeyService', 'ClusterService', 'ToolService', 'ServerService', '$modal', 'OSDConfigService', 'RequestTrackingService', 'ConfigurationService', RootController];
    });
})();
<|endoftext|>


<commit_before>var tmp = require('tmp');
var express = require('express');
var app = express();
app.use(express.bodyParser());

app.get('/vocalization/:voix', function (request, response) {
  response.header('Content-Type', 'audio/mpeg');
  response.header('Content-Disposition', 'inline; filename=test.mp3');
  var spawn = require('child_process').spawn;

  tmp.tmpName(function _tempNameGenerated(err, espeakTmpfile) {
    if (err) throw err;
    var decodedText = decodeURIComponent(request.query.texte);
    var espeak = spawn('espeak', ['-vmb-' + request.params.voix , '-w' + espeakTmpfile , '-s130', decodedText]);

    espeak.on('exit', function(exitCode){
      tmp.tmpName(function _tempNameGenerated(err, lameTmpfile) {
        if (err) throw err;
        // volume normalization with fast replaygain is used by default.
        var options = ['-r', '-mm', '--silent', '-b24', '-s16', espeakTmpfile, lameTmpfile];
        var lame = spawn('lame', options);

        lame.on('exit', function(exitCode){
          response.sendfile(lameTmpfile);
        });

        lame.stderr.on('data', function(data){
          console.log("Lame error: " + data);
        });

      });
    });

    espeak.stderr.on('data', function(data){
      console.log("Espeak error: " + data);
    });

  });

});

module.exports = app;
<commit_msg>Remove unneeded content-disposition header.<commit_after>var tmp = require('tmp');
var express = require('express');
var app = express();
app.use(express.bodyParser());

app.get('/vocalization/:voix', function (request, response) {
  response.header('Content-Type', 'audio/mpeg');
  var spawn = require('child_process').spawn;

  tmp.tmpName(function _tempNameGenerated(err, espeakTmpfile) {
    if (err) throw err;
    var decodedText = decodeURIComponent(request.query.texte);
    var espeak = spawn('espeak', ['-vmb-' + request.params.voix , '-w' + espeakTmpfile , '-s130', decodedText]);

    espeak.on('exit', function(exitCode){
      tmp.tmpName(function _tempNameGenerated(err, lameTmpfile) {
        if (err) throw err;
        // volume normalization with fast replaygain is used by default.
        var options = ['-r', '-mm', '--silent', '-b24', '-s16', espeakTmpfile, lameTmpfile];
        var lame = spawn('lame', options);

        lame.on('exit', function(exitCode){
          response.sendfile(lameTmpfile);
        });

        lame.stderr.on('data', function(data){
          console.log("Lame error: " + data);
        });

      });
    });

    espeak.stderr.on('data', function(data){
      console.log("Espeak error: " + data);
    });

  });

});

module.exports = app;
<|endoftext|>


<commit_before>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;
const FB_TEXT_LIMIT = 640;

class FacebookBot {
    constructor() {
        this.apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
        this.sessionIds = new Map();
        this.messagesDelay = 200;
    }

    doDataResponse(sender, facebookResponseData) {
        if (!Array.isArray(facebookResponseData)) {
            console.log('Response as formatted message');
            this.sendFBMessage(sender, facebookResponseData)
                .catch(err => console.error(err));
        } else {
            async.eachSeries(facebookResponseData, (facebookMessage, callback) => {
                if (facebookMessage.sender_action) {
                    console.log('Response as sender action');
                    this.sendFBSenderAction(sender, facebookMessage.sender_action)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
                else {
                    console.log('Response as formatted message');
                    this.sendFBMessage(sender, facebookMessage)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
            }, (err) => {
                if (err) {
                    console.error(err);
                } else {
                    console.log('Data response completed');
                }
            });
        }
    }

    doRichContentResponse(sender, messages) {
        let facebookMessages = []; // array with result messages

        for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
            let message = messages[messageIndex];

            switch (message.type) {
                //message.type 0 means text message
                case 0:
                    // speech: ["hi"]
                    // we have to get value from fulfillment.speech, because of here is raw speech
                    if (message.speech) {

                        let splittedText = this.splitResponse(message.speech);

                        splittedText.forEach(s => {
                            facebookMessages.push({text: s});
                        });
                    }

                    break;
                //message.type 1 means card message
                case 1: {
                    let carousel = [message];

                    for (messageIndex++; messageIndex < messages.length; messageIndex++) {
                        if (messages[messageIndex].type == 1) {
                            carousel.push(messages[messageIndex]);
                        } else {
                            messageIndex--;
                            break;
                        }
                    }

                    let facebookMessage = {};
                    carousel.forEach((c) => {
                        // buttons: [ {text: "hi", postback: "postback"} ], imageUrl: "", title: "", subtitle: ""

                        let card = {};

                        card.title = c.title;
                        card.image_url = c.imageUrl;
                        if (this.isDefined(c.subtitle)) {
                            card.subtitle = c.subtitle;
                        }
                        //If button is involved in.
                        if (c.buttons.length > 0) {
                            let buttons = [];
                            for (let buttonIndex = 0; buttonIndex < c.buttons.length; buttonIndex++) {
                                let button = c.buttons[buttonIndex];

                                if (button.text) {
                                    let postback = button.postback;
                                    if (!postback) {
                                        postback = button.text;
                                    }

                                    let buttonDescription = {
                                        title: button.text
                                    };

                                    if (postback.startsWith("http")) {
                                        buttonDescription.type = "web_url";
                                        buttonDescription.url = postback;
                                    } else {
                                        buttonDescription.type = "postback";
                                        buttonDescription.payload = postback;
                                    }

                                    buttons.push(buttonDescription);
                                }
                            }

                            if (buttons.length > 0) {
                                card.buttons = buttons;
                            }
                        }

                        if (!facebookMessage.attachment) {
                            facebookMessage.attachment = {type: "template"};
                        }

                        if (!facebookMessage.attachment.payload) {
                            facebookMessage.attachment.payload = {template_type: "generic", elements: []};
                        }

                        facebookMessage.attachment.payload.elements.push(card);
                    });

                    facebookMessages.push(facebookMessage);
                }

                    break;
                //message.type 2 means quick replies message
                case 2: {
                    if (message.replies && message.replies.length > 0) {
                        let facebookMessage = {};

                        facebookMessage.text = message.title ? message.title : 'Choose an item';
                        facebookMessage.quick_replies = [];

                        message.replies.forEach((r) => {
                            facebookMessage.quick_replies.push({
                                content_type: "text",
                                title: r,
                                payload: r
                            });
                        });

                        facebookMessages.push(facebookMessage);
                    }
                }

                    break;
                //message.type 3 means image message
                case 3:

                    if (message.imageUrl) {
                        let facebookMessage = {};

                        // "imageUrl": "http://example.com/image.jpg"
                        facebookMessage.attachment = {type: "image"};
                        facebookMessage.attachment.payload = {url: message.imageUrl};

                        facebookMessages.push(facebookMessage);
                    }

                    break;
                //message.type 4 means custom payload message
                case 4:
                    if (message.payload && message.payload.facebook) {
                        facebookMessages.push(message.payload.facebook);
                    }
                    break;

                default:
                    break;
            }
        }

        return new Promise((resolve, reject) => {
            async.eachSeries(facebookMessages, (msg, callback) => {
                    this.sendFBSenderAction(sender, "typing_on")
                        .then(() => this.sleep(this.messagesDelay))
                        .then(() => this.sendFBMessage(sender, msg))
                        .then(() => callback())
                        .catch(callback);
                },
                (err) => {
                    if (err) {
                        console.error(err);
                        reject(err);
                    } else {
                        console.log('Messages sent');
                        resolve();
                    }
                });
        });

    }

    doTextResponse(sender, responseText) {
        console.log('Response as text message');
        // facebook API limit for text length is 640,
        // so we must split message if needed
        let splittedText = this.splitResponse(responseText);

        async.eachSeries(splittedText, (textPart, callback) => {
            this.sendFBMessage(sender, {text: textPart})
                .then(() => callback())
                .catch(err => callback(err));
        });
    }
    //which webhook event
    getEventText(event) {
        if (event.message) {
            if (event.message.quick_reply && event.message.quick_reply.payload) {
                return event.message.quick_reply.payload;
            }

            if (event.message.text) {
                return event.message.text;
            }
        }

        if (event.postback) {

            if (isDefined(event.postback.referral.ref) && event.postback.payload) {
                return event.postback.referral.ref;
            }

             if (event.postback.payload) {
                return event.postback.payload;
            }
        }

        return null;

    }

    processEvent(event) {
        const sender = event.sender.id.toString();
        const text = this.getEventText(event);

        if (text) {

            // Handle a text message from the sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            console.log("Text", text);
            //send user's text to api.ai service
            let apiaiRequest = this.apiAiService.textRequest(text,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });

            //get response from api.ai
            apiaiRequest.on('response', (response) => {
                if (this.isDefined(response.result) && this.isDefined(response.result.fulfillment)) {
                    let responseText = response.result.fulfillment.speech;
                    let responseData = response.result.fulfillment.data;
                    let responseMessages = response.result.fulfillment.messages;

                    let action = response.result.action;

                    if (this.isDefined(responseData) && this.isDefined(responseData.facebook)) {
                        let facebookResponseData = responseData.facebook;
                        this.doDataResponse(sender, facebookResponseData);
                    } else if (this.isDefined(responseMessages) && responseMessages.length > 0) {
                        this.doRichContentResponse(sender, responseMessages);
                    }
                    else if (this.isDefined(responseText)) {
                        this.doTextResponse(sender, responseText);
                    }

                }
            });

            apiaiRequest.on('error', (error) => console.error(error));
            apiaiRequest.end();
        }
    }

    splitResponse(str) {
        if (str.length <= FB_TEXT_LIMIT) {
            return [str];
        }

        return this.chunkString(str, FB_TEXT_LIMIT);
    }

    chunkString(s, len) {
        let curr = len, prev = 0;

        let output = [];

        while (s[curr]) {
            if (s[curr++] == ' ') {
                output.push(s.substring(prev, curr));
                prev = curr;
                curr += len;
            }
            else {
                let currReverse = curr;
                do {
                    if (s.substring(currReverse - 1, currReverse) == ' ') {
                        output.push(s.substring(prev, currReverse));
                        prev = currReverse;
                        curr = currReverse + len;
                        break;
                    }
                    currReverse--;
                } while (currReverse > prev)
            }
        }
        output.push(s.substr(prev));
        return output;
    }

    sendFBMessage(sender, messageData) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    message: messageData
                }
            }, (error, response) => {
                if (error) {
                    console.log('Error sending message: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.log('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    sendFBSenderAction(sender, action) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    sender_action: action
                }
            }, (error, response) => {
                if (error) {
                    console.error('Error sending action: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.error('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    doSubscribeRequest() {
        request({
                method: 'POST',
                uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription: ', error);
                } else {
                    console.log('Subscription result: ', response.body);
                }
            });
    }

    isDefined(obj) {
        if (typeof obj == 'undefined') {
            return false;
        }

        if (!obj) {
            return false;
        }

        return obj != null;
    }

    sleep(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }

}

let facebookBot = new FacebookBot();

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            facebookBot.doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        const data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            facebookBot.processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

facebookBot.doSubscribeRequest();
<commit_msg>Update app.js<commit_after>'use strict';

const apiai = require('apiai');
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('node-uuid');
const request = require('request');
const JSONbig = require('json-bigint');
const async = require('async');

const REST_PORT = (process.env.PORT || 5000);
const APIAI_ACCESS_TOKEN = process.env.APIAI_ACCESS_TOKEN;
const APIAI_LANG = process.env.APIAI_LANG || 'en';
const FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN;
const FB_PAGE_ACCESS_TOKEN = process.env.FB_PAGE_ACCESS_TOKEN;
const FB_TEXT_LIMIT = 640;

class FacebookBot {
    constructor() {
        this.apiAiService = apiai(APIAI_ACCESS_TOKEN, {language: APIAI_LANG, requestSource: "fb"});
        this.sessionIds = new Map();
        this.messagesDelay = 200;
    }

    doDataResponse(sender, facebookResponseData) {
        if (!Array.isArray(facebookResponseData)) {
            console.log('Response as formatted message');
            this.sendFBMessage(sender, facebookResponseData)
                .catch(err => console.error(err));
        } else {
            async.eachSeries(facebookResponseData, (facebookMessage, callback) => {
                if (facebookMessage.sender_action) {
                    console.log('Response as sender action');
                    this.sendFBSenderAction(sender, facebookMessage.sender_action)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
                else {
                    console.log('Response as formatted message');
                    this.sendFBMessage(sender, facebookMessage)
                        .then(() => callback())
                        .catch(err => callback(err));
                }
            }, (err) => {
                if (err) {
                    console.error(err);
                } else {
                    console.log('Data response completed');
                }
            });
        }
    }

    doRichContentResponse(sender, messages) {
        let facebookMessages = []; // array with result messages

        for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
            let message = messages[messageIndex];

            switch (message.type) {
                //message.type 0 means text message
                case 0:
                    // speech: ["hi"]
                    // we have to get value from fulfillment.speech, because of here is raw speech
                    if (message.speech) {

                        let splittedText = this.splitResponse(message.speech);

                        splittedText.forEach(s => {
                            facebookMessages.push({text: s});
                        });
                    }

                    break;
                //message.type 1 means card message
                case 1: {
                    let carousel = [message];

                    for (messageIndex++; messageIndex < messages.length; messageIndex++) {
                        if (messages[messageIndex].type == 1) {
                            carousel.push(messages[messageIndex]);
                        } else {
                            messageIndex--;
                            break;
                        }
                    }

                    let facebookMessage = {};
                    carousel.forEach((c) => {
                        // buttons: [ {text: "hi", postback: "postback"} ], imageUrl: "", title: "", subtitle: ""

                        let card = {};

                        card.title = c.title;
                        card.image_url = c.imageUrl;
                        if (this.isDefined(c.subtitle)) {
                            card.subtitle = c.subtitle;
                        }
                        //If button is involved in.
                        if (c.buttons.length > 0) {
                            let buttons = [];
                            for (let buttonIndex = 0; buttonIndex < c.buttons.length; buttonIndex++) {
                                let button = c.buttons[buttonIndex];

                                if (button.text) {
                                    let postback = button.postback;
                                    if (!postback) {
                                        postback = button.text;
                                    }

                                    let buttonDescription = {
                                        title: button.text
                                    };

                                    if (postback.startsWith("http")) {
                                        buttonDescription.type = "web_url";
                                        buttonDescription.url = postback;
                                    } else {
                                        buttonDescription.type = "postback";
                                        buttonDescription.payload = postback;
                                    }

                                    buttons.push(buttonDescription);
                                }
                            }

                            if (buttons.length > 0) {
                                card.buttons = buttons;
                            }
                        }

                        if (!facebookMessage.attachment) {
                            facebookMessage.attachment = {type: "template"};
                        }

                        if (!facebookMessage.attachment.payload) {
                            facebookMessage.attachment.payload = {template_type: "generic", elements: []};
                        }

                        facebookMessage.attachment.payload.elements.push(card);
                    });

                    facebookMessages.push(facebookMessage);
                }

                    break;
                //message.type 2 means quick replies message
                case 2: {
                    if (message.replies && message.replies.length > 0) {
                        let facebookMessage = {};

                        facebookMessage.text = message.title ? message.title : 'Choose an item';
                        facebookMessage.quick_replies = [];

                        message.replies.forEach((r) => {
                            facebookMessage.quick_replies.push({
                                content_type: "text",
                                title: r,
                                payload: r
                            });
                        });

                        facebookMessages.push(facebookMessage);
                    }
                }

                    break;
                //message.type 3 means image message
                case 3:

                    if (message.imageUrl) {
                        let facebookMessage = {};

                        // "imageUrl": "http://example.com/image.jpg"
                        facebookMessage.attachment = {type: "image"};
                        facebookMessage.attachment.payload = {url: message.imageUrl};

                        facebookMessages.push(facebookMessage);
                    }

                    break;
                //message.type 4 means custom payload message
                case 4:
                    if (message.payload && message.payload.facebook) {
                        facebookMessages.push(message.payload.facebook);
                    }
                    break;

                default:
                    break;
            }
        }

        return new Promise((resolve, reject) => {
            async.eachSeries(facebookMessages, (msg, callback) => {
                    this.sendFBSenderAction(sender, "typing_on")
                        .then(() => this.sleep(this.messagesDelay))
                        .then(() => this.sendFBMessage(sender, msg))
                        .then(() => callback())
                        .catch(callback);
                },
                (err) => {
                    if (err) {
                        console.error(err);
                        reject(err);
                    } else {
                        console.log('Messages sent');
                        resolve();
                    }
                });
        });

    }

    doTextResponse(sender, responseText) {
        console.log('Response as text message');
        // facebook API limit for text length is 640,
        // so we must split message if needed
        let splittedText = this.splitResponse(responseText);

        async.eachSeries(splittedText, (textPart, callback) => {
            this.sendFBMessage(sender, {text: textPart})
                .then(() => callback())
                .catch(err => callback(err));
        });
    }
    //which webhook event
    getEventText(event) {
        if (event.message) {
            if (event.message.quick_reply && event.message.quick_reply.payload) {
                return event.message.quick_reply.payload;
            }

            if (event.message.text) {
                return event.message.text;
            }
        }

        if (event.postback) {

            if (isDefined(event.postback.referral.ref) && event.postback.payload) {
                return event.postback.referral.ref;
            }

             //if (event.postback.payload) {
               // return event.postback.payload;
            //}
        }

        return null;

    }

    processEvent(event) {
        const sender = event.sender.id.toString();
        const text = this.getEventText(event);

        if (text) {

            // Handle a text message from the sender
            if (!this.sessionIds.has(sender)) {
                this.sessionIds.set(sender, uuid.v4());
            }

            console.log("Text", text);
            //send user's text to api.ai service
            let apiaiRequest = this.apiAiService.textRequest(text,
                {
                    sessionId: this.sessionIds.get(sender),
                    originalRequest: {
                        data: event,
                        source: "facebook"
                    }
                });

            //get response from api.ai
            apiaiRequest.on('response', (response) => {
                if (this.isDefined(response.result) && this.isDefined(response.result.fulfillment)) {
                    let responseText = response.result.fulfillment.speech;
                    let responseData = response.result.fulfillment.data;
                    let responseMessages = response.result.fulfillment.messages;

                    let action = response.result.action;

                    if (this.isDefined(responseData) && this.isDefined(responseData.facebook)) {
                        let facebookResponseData = responseData.facebook;
                        this.doDataResponse(sender, facebookResponseData);
                    } else if (this.isDefined(responseMessages) && responseMessages.length > 0) {
                        this.doRichContentResponse(sender, responseMessages);
                    }
                    else if (this.isDefined(responseText)) {
                        this.doTextResponse(sender, responseText);
                    }

                }
            });

            apiaiRequest.on('error', (error) => console.error(error));
            apiaiRequest.end();
        }
    }

    splitResponse(str) {
        if (str.length <= FB_TEXT_LIMIT) {
            return [str];
        }

        return this.chunkString(str, FB_TEXT_LIMIT);
    }

    chunkString(s, len) {
        let curr = len, prev = 0;

        let output = [];

        while (s[curr]) {
            if (s[curr++] == ' ') {
                output.push(s.substring(prev, curr));
                prev = curr;
                curr += len;
            }
            else {
                let currReverse = curr;
                do {
                    if (s.substring(currReverse - 1, currReverse) == ' ') {
                        output.push(s.substring(prev, currReverse));
                        prev = currReverse;
                        curr = currReverse + len;
                        break;
                    }
                    currReverse--;
                } while (currReverse > prev)
            }
        }
        output.push(s.substr(prev));
        return output;
    }

    sendFBMessage(sender, messageData) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    message: messageData
                }
            }, (error, response) => {
                if (error) {
                    console.log('Error sending message: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.log('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    sendFBSenderAction(sender, action) {
        return new Promise((resolve, reject) => {
            request({
                url: 'https://graph.facebook.com/v2.6/me/messages',
                qs: {access_token: FB_PAGE_ACCESS_TOKEN},
                method: 'POST',
                json: {
                    recipient: {id: sender},
                    sender_action: action
                }
            }, (error, response) => {
                if (error) {
                    console.error('Error sending action: ', error);
                    reject(error);
                } else if (response.body.error) {
                    console.error('Error: ', response.body.error);
                    reject(new Error(response.body.error));
                }

                resolve();
            });
        });
    }

    doSubscribeRequest() {
        request({
                method: 'POST',
                uri: "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=" + FB_PAGE_ACCESS_TOKEN
            },
            (error, response, body) => {
                if (error) {
                    console.error('Error while subscription: ', error);
                } else {
                    console.log('Subscription result: ', response.body);
                }
            });
    }

    isDefined(obj) {
        if (typeof obj == 'undefined') {
            return false;
        }

        if (!obj) {
            return false;
        }

        return obj != null;
    }

    sleep(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }

}

let facebookBot = new FacebookBot();

const app = express();

app.use(bodyParser.text({type: 'application/json'}));

app.get('/webhook/', (req, res) => {
    if (req.query['hub.verify_token'] == FB_VERIFY_TOKEN) {
        res.send(req.query['hub.challenge']);

        setTimeout(() => {
            facebookBot.doSubscribeRequest();
        }, 3000);
    } else {
        res.send('Error, wrong validation token');
    }
});

app.post('/webhook/', (req, res) => {
    try {
        const data = JSONbig.parse(req.body);

        if (data.entry) {
            let entries = data.entry;
            entries.forEach((entry) => {
                let messaging_events = entry.messaging;
                if (messaging_events) {
                    messaging_events.forEach((event) => {
                        if (event.message && !event.message.is_echo ||
                            event.postback && event.postback.payload) {
                            facebookBot.processEvent(event);
                        }
                    });
                }
            });
        }

        return res.status(200).json({
            status: "ok"
        });
    } catch (err) {
        return res.status(400).json({
            status: "error",
            error: err
        });
    }

});

app.listen(REST_PORT, () => {
    console.log('Rest service ready on port ' + REST_PORT);
});

facebookBot.doSubscribeRequest();
<|endoftext|>


<commit_before>const payexCheckout = require('./payex.checkout');
const paymentSession = require('./paymentsession');
const view = require('./view');
var server = null;

module.exports = {
    start: start,
    showIndex: showIndex,
    submitOrder: submitOrder,
    showReceipt: showReceipt,
}

/*
 * Starts the web application.
 *
 * Bootstraps with the environment variable ACCESS_TOKEN, used to authorize
 * against PayEx Checkout.
 *
 */
function start(s) {
    server = s;
    require('dotenv').config();
    var accessToken = process.env.ACCESS_TOKEN;
    if (!accessToken) {
        console.error('No access token configured. Have you created an .env file with ACCESS_TOKEN=<ACCESS_TOKEN> in it?');
        accessToken = '<NO_ACCESS_TOKEN_CONFIGURED>';
    }
    console.log(`Bootstrapping with Access Token: ${accessToken}.`)
    payexCheckout(process.env.ACCESS_TOKEN).then(init => {
        s.locals.payexCheckout = init;
        s.listen(process.env.PORT || 3000, () => {
            console.log('Listening on http://localhost:' + (process.env.PORT || 3000))
        });
    });
}

/*
 * Handles the rendering of the index page.
 *
 * The home page, displaying the fluffy animals you can buy.
 * Should perform a series of POST requests to PayEx Checkout to create
 * one Payment Session for each fluffy animal.
 *
 */
function showIndex(request, response, next) {
    try {
        const checkout = server.locals.payexCheckout;
        const createPaymentSessions = paymentSession
            .initialize()
            .map(checkout.createPaymentSession)

        Promise.all(createPaymentSessions).then(paymentSessions => {
            var model = {
                paymentSessions: paymentSessions
            };
            view.render('index', model, response, next);
        }).catch(e => {
            console.error(e);
            var model = {
                error: e
            };
            view.render('error', model, response, next);
        });
    } catch (e) {
		console.error(e);
        next(e);
    }
}

/*
 * Handles the submission of the order.
 *
 * Invoked after the PayEx Checkout is complete. The posted form will have
 * `paymentSession` as a hidden field, containing the URL of the Payment
 * Session that was purchased.
 *
 * Performs capture on the created Payment and redirects to the receipt.
 *
 */
function submitOrder(request, response, next) {
    try {
        const checkout = server.locals.payexCheckout;
        const paymentSession = request.body.paymentSession;

        checkout.capture(paymentSession).then(result => {
            const url = `/receipt?ps=${paymentSession}&state=${result.state}&amount=${result.amount}`;
            console.log('Redirecting to:', url);
            response.redirect(url);
        }).catch(e => {
            console.error(e);
            var model = {
                error: e
            };
            view.render('error', model, response, next);
        });
    } catch (e) {
        console.error(e);
        next(e);
    }
}

/*
 * Handles the rendering of the receipt page.
 *
 * After the POST performing capture is complete, it redirects to this page,
 * displaying the status about the captured payment.
 *
 */
function showReceipt(request, response, next) {
    try {
        var amount = parseFloat(Math.round(request.query.amount * 100) / 100).toFixed(2);
		var model = {
            paymentSession: request.query.ps,
            state: request.query.state,
            amount: amount
        };
		view.render('receipt', model, response, next);
    } catch (e) {
        console.error(e);
        next(e);
    }
}
<commit_msg>Moved error page rendering to a showError function<commit_after>const payexCheckout = require('./payex.checkout');
const paymentSession = require('./paymentsession');
const view = require('./view');
var server = null;

module.exports = {
    start: start,
    showIndex: showIndex,
    submitOrder: submitOrder,
    showReceipt: showReceipt,
}

/*
 * Starts the web application.
 *
 * Bootstraps with the environment variable ACCESS_TOKEN, used to authorize
 * against PayEx Checkout.
 *
 */
function start(s) {
    server = s;
    require('dotenv').config();
    var accessToken = process.env.ACCESS_TOKEN;
    if (!accessToken) {
        console.error('No access token configured. Have you created an .env file with ACCESS_TOKEN=<ACCESS_TOKEN> in it?');
        accessToken = '<NO_ACCESS_TOKEN_CONFIGURED>';
    }
    console.log(`Bootstrapping with Access Token: ${accessToken}.`)
    payexCheckout(process.env.ACCESS_TOKEN).then(init => {
        s.locals.payexCheckout = init;
        s.listen(process.env.PORT || 3000, () => {
            console.log('Listening on http://localhost:' + (process.env.PORT || 3000))
        });
    });
}

/*
 * Handles the rendering of the index page.
 *
 * The home page, displaying the fluffy animals you can buy.
 * Should perform a series of POST requests to PayEx Checkout to create
 * one Payment Session for each fluffy animal.
 *
 */
function showIndex(request, response, next) {
    try {
        const checkout = server.locals.payexCheckout;
        const createPaymentSessions = paymentSession
            .initialize()
            .map(checkout.createPaymentSession)

        Promise.all(createPaymentSessions).then(paymentSessions => {
            var model = {
                paymentSessions: paymentSessions
            };
            view.render('index', model, response, next);
        }).catch(error => showError(error, response, next));
    } catch (error) {
        showError(error, response, next);
    }
}

/*
 * Handles the submission of the order.
 *
 * Invoked after the PayEx Checkout is complete. The posted form will have
 * `paymentSession` as a hidden field, containing the URL of the Payment
 * Session that was purchased.
 *
 * Performs capture on the created Payment and redirects to the receipt.
 *
 */
function submitOrder(request, response, next) {
    try {
        const checkout = server.locals.payexCheckout;
        const paymentSession = request.body.paymentSession;

        checkout.capture(paymentSession).then(result => {
            const url = `/receipt?ps=${paymentSession}&state=${result.state}&amount=${result.amount}`;
            console.log('Redirecting to:', url);
            response.redirect(url);
        }).catch(error => showError(error, response, next));
    } catch (error) {
        showError(error, response, next);
    }
}

/*
 * Handles the rendering of the receipt page.
 *
 * After the POST performing capture is complete, it redirects to this page,
 * displaying the status about the captured payment.
 *
 */
function showReceipt(request, response, next) {
    try {
		var model = {
            paymentSession: request.query.ps,
            state: request.query.state,
            amount: parseFloat(Math.round(request.query.amount * 100) / 100).toFixed(2)
        };
		view.render('receipt', model, response, next);
    } catch (error) {
        showError(error, response, next);
    }
}

/*
 * Handles the rendering of an error.
 *
 */
function showError(error, response, next) {
    console.error(error);

    try {
        var model = {
            error: error
        };
        view.render('error', model, response, next);
    } catch (e) {
        console.error(e);
        next(e);
    }
}
<|endoftext|>


<commit_before>import feathers from '@feathersjs/feathers';
import rest from '@feathersjs/rest-client';
import socketio from '@feathersjs/socketio-client';
import authentication from '@feathersjs/authentication-client';
import io from 'socket.io-client';
import axios from 'axios';
import config from './config';

const storage = __SERVER__ ? null : require('localforage');

const host = clientUrl => (__SERVER__ ? `http://${config.apiHost}:${config.apiPort}` : clientUrl);

const configureApp = transport => feathers()
  .configure(transport)
  .configure(authentication({ storage }));

export const socket = io('', { path: host('/ws'), autoConnect: false });

export function createApp(req) {
  if (req === 'rest') {
    return configureApp(rest(host('/api')).axios(axios));
  }

  if (__SERVER__ && req) {
    const token = req.header('authorization') || (req.cookies && req.cookies['feathers-jwt']);
    const app = configureApp(
      rest(host('/api')).axios(
        axios.create({
          headers: {
            Cookie: req.get('cookie'),
            Authorization: token
          }
        })
      )
    );

    app.set('accessToken', token);

    return app;
  }

  return configureApp(socketio(socket));
}
<commit_msg>fix app.js<commit_after>import feathers from '@feathersjs/feathers';
import rest from '@feathersjs/rest-client';
import socketio from '@feathersjs/socketio-client';
import authentication from '@feathersjs/authentication-client';
import io from 'socket.io-client';
import axios from 'axios';
import config from './config';

const storage = __SERVER__ ? null : require('localforage');

const host = clientUrl => (__SERVER__ ? `http://${config.apiHost}:${config.apiPort}` : clientUrl);

const configureApp = transport => feathers()
  .configure(transport)
  .configure(authentication({ storage }));

export const socket = io('', { path: host('/ws'), autoConnect: false });

export function createApp(req) {
  if (req === 'rest') {
    return configureApp(rest(host('/api')).axios(axios));
  }

  if (__SERVER__ && req) {
    const app = configureApp(
      rest(host('/api')).axios(
        axios.create({
          headers: {
            Cookie: req.get('cookie'),
            Authorization: req.header('authorization')
          }
        })
      )
    );

    app.set('accessToken', req.cookies && req.cookies['feathers-jwt']);

    return app;
  }

  return configureApp(socketio(socket));
}
<|endoftext|>


<commit_before>/*global Promise:true */
'use strict';

Promise = require('bluebird');

let TwitterAdaptor = require('./adaptors/TwitterAdaptor');
let TwitterSentimentDecorator = require('./decorators/TweetSentimentDecorator');

let totalScore = 0;
let positiveWords = [];
let negativeWords = [];

let twitterAdaptor = new TwitterAdaptor().getTweets("@producthunt", 10).all().then(tweets => {
    tweets = [].concat.apply([], tweets)

    tweets.map(tweet => {
        return new TwitterSentimentDecorator().decorate(tweet);
    }).forEach(tweet => {
        totalScore += tweet.sentiment.score;
        positiveWords = positiveWords.concat(tweet.sentiment.positive);
        negativeWords = negativeWords.concat(tweet.sentiment.negative);
    });
    console.log("A sample size of " + tweets.length + " tweets yielded a " + totalScore/tweets.length + " average sentiment score with " + positiveWords.length + " of words being positive and " + negativeWords.length + " being negative.");
});<commit_msg>Fix master<commit_after>/*global Promise:true */
'use strict';

Promise = require('bluebird');

let TwitterAdaptor = require('./adaptors/TwitterAdaptor');
let TwitterSentimentDecorator = require('./decorators/TweetSentimentDecorator');

let totalScore = 0;
let positiveWords = [];
let negativeWords = [];

let twitterAdaptor = new TwitterAdaptor().getTweets("@producthunt", 10).all().then(tweets => {
    tweets = [].concat.apply([], tweets);

    tweets.map(tweet => {
        return new TwitterSentimentDecorator().decorate(tweet);
    }).forEach(tweet => {
        totalScore += tweet.sentiment.score;
        positiveWords = positiveWords.concat(tweet.sentiment.positive);
        negativeWords = negativeWords.concat(tweet.sentiment.negative);
    });
    console.log("A sample size of " + tweets.length + " tweets yielded a " + totalScore/tweets.length + " average sentiment score with " + positiveWords.length + " of words being positive and " + negativeWords.length + " being negative.");
});<|endoftext|>


<commit_before>// Global variables
let mazeNodes = {};

// Check if globals are defined
if (typeof maxMaze === 'undefined') {
	maxMaze = 0;
}

if (typeof maxSolve === 'undefined') {
	maxSolve = 0;
}

if (typeof maxCanvas === 'undefined') {
	maxCanvas = 0;
}

if (typeof maxCanvasDimension === 'undefined') {
	maxCanvasDimension = 0;
}

if (typeof maxWallsRemove === 'undefined') {
	maxWallsRemove = 300;
}

// Update remove max walls html
const removeMaxWallsText = document.querySelector('.desc span');
if (removeMaxWallsText) {
	removeMaxWallsText.innerHTML = maxWallsRemove;
}

const removeWallsInput = document.getElementById('remove_walls');
if (removeWallsInput) {
	removeWallsInput.max = maxWallsRemove;
}

const download = document.getElementById("download");
download.addEventListener("click", downloadImage, false);
download.setAttribute('download', 'maze.png');

function initMaze() {
	const settings = {
		width: getInputIntVal('width', 20),
		height: getInputIntVal('height', 20),
		wallSize: getInputIntVal('wall-size', 10),
		removeWalls: getInputIntVal('remove_walls', 0),
		entryType: '',
		bias: '',
		color: '#000000',
		backgroudColor: '#FFFFFF',
		solveColor: '#cc3737',

		// restrictions
		maxMaze: maxMaze,
		maxCanvas: maxCanvas,
		maxCanvasDimension: maxCanvasDimension,
		maxSolve: maxSolve,
		maxWallsRemove: maxWallsRemove,
	}

	const colors = ['color', 'backgroundColor', 'solveColor'];
	for (let i = 0; i < colors.length; i++) {
		const colorInput = document.getElementById(colors[i]);
		settings[colors[i]] = colorInput.value
		if (!isValidHex(settings[colors[i]])) {
			let defaultColor = colorInput.parentNode.dataset.default;
			colorInput.value = defaultColor;
			settings[colors[i]] = defaultColor;
		}

		const colorSample = colorInput.parentNode.querySelector('.color-sample');
		colorSample.style = 'background-color: ' + settings[colors[i]] + ';';
	}

	if (settings['removeWalls'] > maxWallsRemove) {
		settings['removeWalls'] = maxWallsRemove;
		if (removeWallsInput) {
			removeWallsInput.value = maxWallsRemove;
		}
	}

	const entry = document.getElementById('entry');
	if (entry) {
		settings['entryType'] = entry.options[entry.selectedIndex].value;
	}

	const bias = document.getElementById('bias');
	if (bias) {
		settings['bias'] = bias.options[bias.selectedIndex].value;
	}

	const maze = new Maze(settings);
	maze.generate();
	maze.draw();

	if (download && download.classList.contains('hide')) {
		download.classList.toggle("hide");
	}

	const solveButton = document.getElementById("solve");
	if (solveButton && solveButton.classList.contains('hide')) {
		solveButton.classList.toggle("hide");
	}

	mazeNodes = {}
	if (maze.matrix.length) {
		mazeNodes = maze;
	}

	location.href = "#";
	location.href = "#generate";
}

function downloadImage(e) {
	const image = document.getElementById('maze').toDataURL("image/png");
    image.replace("image/png", "image/octet-stream");
	download.setAttribute("href", image);
}

function initSolve() {
	if ((typeof mazeNodes.matrix === 'undefined') || !mazeNodes.matrix.length) {
		return;
	}

	const solver = new Solver(mazeNodes);
	solver.solve();
	if (mazeNodes.wallsRemoved) {
		solver.drawAstarSolve();
	} else {
		solver.draw();
	}
	mazeNodes = {}

	const solveButton = document.getElementById("solve");
	if (solveButton) {
		solveButton.classList.toggle("hide");
	}
}<commit_msg>Add difference for maze and solved maze link text<commit_after>// Global variables
let mazeNodes = {};

// Check if globals are defined
if (typeof maxMaze === 'undefined') {
    maxMaze = 0;
}

if (typeof maxSolve === 'undefined') {
    maxSolve = 0;
}

if (typeof maxCanvas === 'undefined') {
    maxCanvas = 0;
}

if (typeof maxCanvasDimension === 'undefined') {
    maxCanvasDimension = 0;
}

if (typeof maxWallsRemove === 'undefined') {
    maxWallsRemove = 300;
}

// Update remove max walls html
const removeMaxWallsText = document.querySelector('.desc span');
if (removeMaxWallsText) {
    removeMaxWallsText.innerHTML = maxWallsRemove;
}

const removeWallsInput = document.getElementById('remove_walls');
if (removeWallsInput) {
    removeWallsInput.max = maxWallsRemove;
}

const download = document.getElementById("download");
download.addEventListener("click", downloadImage, false);
download.setAttribute('download', 'maze.png');

function initMaze() {
    download.setAttribute('download', 'maze.png');
    download.innerHTML = 'download maze';

    const settings = {
        width: getInputIntVal('width', 20),
        height: getInputIntVal('height', 20),
        wallSize: getInputIntVal('wall-size', 10),
        removeWalls: getInputIntVal('remove_walls', 0),
        entryType: '',
        bias: '',
        color: '#000000',
        backgroudColor: '#FFFFFF',
        solveColor: '#cc3737',

        // restrictions
        maxMaze: maxMaze,
        maxCanvas: maxCanvas,
        maxCanvasDimension: maxCanvasDimension,
        maxSolve: maxSolve,
        maxWallsRemove: maxWallsRemove,
    }

    const colors = ['color', 'backgroundColor', 'solveColor'];
    for (let i = 0; i < colors.length; i++) {
        const colorInput = document.getElementById(colors[i]);
        settings[colors[i]] = colorInput.value
        if (!isValidHex(settings[colors[i]])) {
            let defaultColor = colorInput.parentNode.dataset.default;
            colorInput.value = defaultColor;
            settings[colors[i]] = defaultColor;
        }

        const colorSample = colorInput.parentNode.querySelector('.color-sample');
        colorSample.style = 'background-color: ' + settings[colors[i]] + ';';
    }

    if (settings['removeWalls'] > maxWallsRemove) {
        settings['removeWalls'] = maxWallsRemove;
        if (removeWallsInput) {
            removeWallsInput.value = maxWallsRemove;
        }
    }

    const entry = document.getElementById('entry');
    if (entry) {
        settings['entryType'] = entry.options[entry.selectedIndex].value;
    }

    const bias = document.getElementById('bias');
    if (bias) {
        settings['bias'] = bias.options[bias.selectedIndex].value;
    }

    const maze = new Maze(settings);
    maze.generate();
    maze.draw();

    if (download && download.classList.contains('hide')) {
        download.classList.toggle("hide");
    }

    const solveButton = document.getElementById("solve");
    if (solveButton && solveButton.classList.contains('hide')) {
        solveButton.classList.toggle("hide");
    }

    mazeNodes = {}
    if (maze.matrix.length) {
        mazeNodes = maze;
    }

    location.href = "#";
    location.href = "#generate";
}

function downloadImage(e) {
    const image = document.getElementById('maze').toDataURL("image/png");
    image.replace("image/png", "image/octet-stream");
    download.setAttribute("href", image);
}

function initSolve() {
    const solveButton = document.getElementById("solve");
    if (solveButton) {
        solveButton.classList.toggle("hide");
    }

    download.setAttribute('download', 'maze-solved.png');
    download.innerHTML = 'download solved maze';

    if ((typeof mazeNodes.matrix === 'undefined') || !mazeNodes.matrix.length) {
        return;
    }

    const solver = new Solver(mazeNodes);
    solver.solve();
    if (mazeNodes.wallsRemoved) {
        solver.drawAstarSolve();
    } else {
        solver.draw();
    }

    mazeNodes = {}
}<|endoftext|>