<commit_before>'use strict'

var fs = require('fs')
var os = require('os')
var path = require('path')
var util = require('util')
var debug = require('debug')('opbeat')
var trunc = require('unicode-byte-truncate')

var GENERIC_PLATFORM_HEADER = util.format('lang=node/%s platform=%s os=%s',
    process.versions.node,
    process.release ? process.release.name : 'node',
    process.platform)

var request = function (agent, endpoint, payload, cb) {
  if (!agent.active) {
    // failsafe - we should never reach this point
    debug('request attempted on an inactive agent - ignoring!')
    return
  }

  var headers = {
    'X-Opbeat-Platform': GENERIC_PLATFORM_HEADER + (agent._platform.framework
      ? util.format(' framework=%s/%s', agent._platform.framework.name, agent._platform.framework.version)
      : '')
  }

  if (process.env.OPBEAT_DEBUG_PAYLOAD) capturePayload(endpoint, payload)

  agent._httpClient.request(endpoint, headers, payload, function (err, res, body) {
    if (err) return cb(err)
    if (res.statusCode < 200 || res.statusCode > 299) {
      var msg = util.format('Opbeat error (%d): %s', res.statusCode, body)
      cb(new Error(msg))
      return
    }
    cb(null, res.headers.location)
  })
}

exports.error = function (agent, payload, cb) {
  truncErrorPayload(payload)

  request(agent, 'errors', payload, function (err, url) {
    var uuid = payload.extra && payload.extra.uuid
    if (err) {
      if (cb) cb(err)
      agent.emit('error', err, uuid)
      return
    }
    if (cb) cb(null, url)
    agent.emit('logged', url, uuid)
  })
}

exports.transactions = function (agent, payload, cb) {
  filterTransactionPayload(payload, agent._filters)
  truncTransactionPayload(payload)

  debug('sending transactions to intake api')
  request(agent, 'transactions', payload, function (err) {
    if (err) {
      if (cb) cb(err)
      agent.emit('error', err)
      return
    }
    debug('logged transactions successfully')
  })
}

function filterTransactionPayload (payload, filters) {
  payload.traces.raw.forEach(function (raw) {
    var context = raw[raw.length - 1]
    context = filters.process(context)
    if (!context) raw.pop()
    else raw[raw.length - 1] = context
  })
}

function truncErrorPayload (payload) {
  if (payload.stacktrace && payload.stacktrace.frames) {
    payload.stacktrace.frames = truncFrames(payload.stacktrace.frames)
  }

  if (payload.exception && payload.exception.value) {
    payload.exception.value = trunc(String(payload.exception.value), 2048)
  }

  if (payload.culprit) {
    payload.culprit = trunc(String(payload.culprit), 100)
  }

  if (payload.message) {
    payload.message = trunc(String(payload.message), 200)
  }
}

function truncTransactionPayload (payload) {
  if (payload.transactions) {
    payload.transactions.forEach(function (trans) {
      trans.transaction = trunc(String(trans.transaction), 512)
    })
  }

  if (payload.traces && payload.traces.groups) {
    payload.traces.groups.forEach(function (group) {
      if (group.transaction) group.transaction = trunc(String(group.transaction), 512)
      if (group.extra && group.extra._frames) group.extra._frames = truncFrames(group.extra._frames)
    })
  }
}

function truncFrames (frames) {
  // max 300 stack frames
  if (frames.length > 300) {
    // frames are reversed to match API requirements
    frames = frames.slice(-300)
  }

  // each line in stack trace must not exeed 1000 chars
  frames.forEach(function (frame, i) {
    if (frame.pre_context) frame.pre_context = truncEach(frame.pre_context, 1000)
    if (frame.context_line) frame.context_line = trunc(String(frame.context_line), 1000)
    if (frame.post_context) frame.post_context = truncEach(frame.post_context, 1000)
  })

  return frames
}

function truncEach (arr, len) {
  return arr.map(function (str) {
    return trunc(String(str), len)
  })
}

// Used only for debugging data sent to the intake API
function capturePayload (endpoint, payload) {
  var dumpfile = path.join(os.tmpdir(), 'opbeat-' + endpoint + '-' + Date.now() + '.json')
  fs.writeFile(dumpfile, JSON.stringify(payload), function (err) {
    if (err) debug('could not capture intake payload: %s', err.message)
    else debug('intake payload captured: %s', dumpfile)
  })
}
<commit_msg>core: truncate trace signatures to max 512 chars<commit_after>'use strict'

var fs = require('fs')
var os = require('os')
var path = require('path')
var util = require('util')
var debug = require('debug')('opbeat')
var trunc = require('unicode-byte-truncate')

var GENERIC_PLATFORM_HEADER = util.format('lang=node/%s platform=%s os=%s',
    process.versions.node,
    process.release ? process.release.name : 'node',
    process.platform)

var request = function (agent, endpoint, payload, cb) {
  if (!agent.active) {
    // failsafe - we should never reach this point
    debug('request attempted on an inactive agent - ignoring!')
    return
  }

  var headers = {
    'X-Opbeat-Platform': GENERIC_PLATFORM_HEADER + (agent._platform.framework
      ? util.format(' framework=%s/%s', agent._platform.framework.name, agent._platform.framework.version)
      : '')
  }

  if (process.env.OPBEAT_DEBUG_PAYLOAD) capturePayload(endpoint, payload)

  agent._httpClient.request(endpoint, headers, payload, function (err, res, body) {
    if (err) return cb(err)
    if (res.statusCode < 200 || res.statusCode > 299) {
      var msg = util.format('Opbeat error (%d): %s', res.statusCode, body)
      cb(new Error(msg))
      return
    }
    cb(null, res.headers.location)
  })
}

exports.error = function (agent, payload, cb) {
  truncErrorPayload(payload)

  request(agent, 'errors', payload, function (err, url) {
    var uuid = payload.extra && payload.extra.uuid
    if (err) {
      if (cb) cb(err)
      agent.emit('error', err, uuid)
      return
    }
    if (cb) cb(null, url)
    agent.emit('logged', url, uuid)
  })
}

exports.transactions = function (agent, payload, cb) {
  filterTransactionPayload(payload, agent._filters)
  truncTransactionPayload(payload)

  debug('sending transactions to intake api')
  request(agent, 'transactions', payload, function (err) {
    if (err) {
      if (cb) cb(err)
      agent.emit('error', err)
      return
    }
    debug('logged transactions successfully')
  })
}

function filterTransactionPayload (payload, filters) {
  payload.traces.raw.forEach(function (raw) {
    var context = raw[raw.length - 1]
    context = filters.process(context)
    if (!context) raw.pop()
    else raw[raw.length - 1] = context
  })
}

function truncErrorPayload (payload) {
  if (payload.stacktrace && payload.stacktrace.frames) {
    payload.stacktrace.frames = truncFrames(payload.stacktrace.frames)
  }

  if (payload.exception && payload.exception.value) {
    payload.exception.value = trunc(String(payload.exception.value), 2048)
  }

  if (payload.culprit) {
    payload.culprit = trunc(String(payload.culprit), 100)
  }

  if (payload.message) {
    payload.message = trunc(String(payload.message), 200)
  }
}

function truncTransactionPayload (payload) {
  if (payload.transactions) {
    payload.transactions.forEach(function (trans) {
      trans.transaction = trunc(String(trans.transaction), 512)
    })
  }

  if (payload.traces && payload.traces.groups) {
    payload.traces.groups.forEach(function (group) {
      if (group.transaction) group.transaction = trunc(String(group.transaction), 512)
      if (group.signature) group.signature = trunc(String(group.signature), 512)
      if (group.extra && group.extra._frames) group.extra._frames = truncFrames(group.extra._frames)
    })
  }
}

function truncFrames (frames) {
  // max 300 stack frames
  if (frames.length > 300) {
    // frames are reversed to match API requirements
    frames = frames.slice(-300)
  }

  // each line in stack trace must not exeed 1000 chars
  frames.forEach(function (frame, i) {
    if (frame.pre_context) frame.pre_context = truncEach(frame.pre_context, 1000)
    if (frame.context_line) frame.context_line = trunc(String(frame.context_line), 1000)
    if (frame.post_context) frame.post_context = truncEach(frame.post_context, 1000)
  })

  return frames
}

function truncEach (arr, len) {
  return arr.map(function (str) {
    return trunc(String(str), len)
  })
}

// Used only for debugging data sent to the intake API
function capturePayload (endpoint, payload) {
  var dumpfile = path.join(os.tmpdir(), 'opbeat-' + endpoint + '-' + Date.now() + '.json')
  fs.writeFile(dumpfile, JSON.stringify(payload), function (err) {
    if (err) debug('could not capture intake payload: %s', err.message)
    else debug('intake payload captured: %s', dumpfile)
  })
}
<|endoftext|>


<commit_before>/**
# Require
Copyright ©2016-2017 Tonio Loewald

Simple implementation of CommonJS *require*.

Usage:

    const module = require('path/to/module.js');

Relative paths are supported within a file, so, assuming we're inside
`path/to/module.js` and `another_module.js` is also in `path/to/`:

    const another_module = require('./another_module.js');

If you don't want synchronous requires (and, you don't):

    require.lazy('path/to/module.js').then(module => {
      // you're good to go!
    });

And finally, if you need to use some primordial library that wants
to be global:

    require.viaTag('path/to/babylon.min.js').then(() => {
      const engine = new BABYLON.Engine(canvas, true);
      ...
    });

## Quick and Dirty Async Delivery

Naive use of require will result in console spam about how
synchronous XHR requests are bad. Rather than using a compile phase
to avoid them, you can do this in the console:

    require.preloadData();

This will produce an array of data (in essence the inferred dependency
graph unspooled as "waves" of modules that can be loaded asynchronously
in order to get everything asynchronously loaded in parallel) which you
can pass to:

    require.preload([ ... ]).then(() => {
      // write the code you would have written before,
      // everything will have loaded asynchronously!
    });

Right now, preloadData() tries to load everything in parallel
as quickly as it can (modulo dependencies), meaning that big modules
with no dependencies can be a bottleneck. If this every becomes a problem
for me (including if I get bothered by enough people), I will drill down and
try to even out the preload "waves".

### That was boring, let's do this all automatically!

    require.autoPreload().then(() => {
      // write the code you would have written before,
      // everything will have loaded asynchronously after the first time!
    });

The first time this runs you'll see all the sync loading warnings, and then
it just goes away. Sometimes a code change will cause the preload data to
be automatically updated. Now, this isn't quite as good as working perfectly the
first time, which would require doing this on the server. It would be pretty
easy to integrate this with your devops workflow if it came to that.

    require.autoPreload(10000).then(...); // you can change the throttle delay

Note that the storing of preloadData is throttled because calculating dependencies
could get expensive for sufficiently complex projects. It defaults to 2000ms which
should be fine for anything but truly byzantine sites. It takes < 1ms on a reasonably
modern device for the b8r demo page, but if I recall correctly calculating the
dependencies is O(n^2).
*/
/* global console */

(function(global) {
  'use strict';
  var modules = {};
  const noop = () => {};
  const dependency_map = {};
  const modules_loaded_synchronously = [];
  var autoPreload_enabled = 0; // also used to throttle saving of preload data

  function define(module_name, source_code) {
    var module = {};
    const path = module_name.split('/');
    path.pop();
    /* jshint evil:true */
    var factory = new Function(
        'module', 'require', source_code + `\n//# sourceURL=${module_name}`);
    /* jshint evil:false */
    const local_require = module => {
      if (module.substr(0, 2) === './') {
        module = path.join('/') + module.substr(1);
      } else if (module.substr(0, 3) === '../') {
        path.pop();
        module = path.join('/') + module.substr(2);
      }
      if (!dependency_map[module_name]) {
        dependency_map[module_name] = [];
      }
      if (dependency_map[module_name].indexOf(module) === -1) {
        dependency_map[module_name].push(module);
      }
      return global.require(module);
    };
    local_require.lazy = _lazy;
    factory(module, local_require);
    modules[module_name] = module;
    return module;
  }

  function _require(module_name) {
    if (!modules[module_name]) {
      var request = new XMLHttpRequest();
      modules_loaded_synchronously.push(module_name);
      console.warn(module_name, 'was loaded synchronously');
      if (autoPreload_enabled) {
        _savePreloadData();
      }
      request.open('GET', module_name, false);
      request.send(null);
      if (request.status === 200) {
        define(module_name, request.responseText);
      } else {
        console.error('could not load required module', module_name);
        return {};
      }
    }
    return modules[module_name].exports;
  }

  global.require = _require;

  global.require.isDefined = function(module_name) {
    return !!modules[module_name];
  };

  const _lazy = module_name => {
    return new Promise(function(resolve, reject) {
      if (!modules[module_name]) {
        var request = new XMLHttpRequest();
        request.open('GET', module_name, true);
        request.onload = function(data) {
          if (request.readyState === 4) {
            if (request.status === 200) {
              (resolve ||
               noop)(define(module_name, request.responseText).exports);
            } else {
              (reject || noop)(request, data);
            }
          }
        };
        request.onerror = function(data) {
          (reject || noop)(request, data);
        };
        request.send(null);
      } else {
        setTimeout(() => resolve && resolve(modules[module_name].exports));
      }
    });
  };

  const scriptTags = {};
  const _viaTag = script_path => {
    if(!scriptTags[script_path]) {
      scriptTags[script_path] = new Promise((resolve) => {
        const script = document.createElement('script');
        script.setAttribute('src', script_path);
        script.addEventListener('load', resolve);
        document.body.appendChild(script);
      });
    }
    return scriptTags[script_path];
  };

  var savePreloadDataTimeout = 0;
  const _savePreloadData = () => {
    if (savePreloadDataTimeout) {
      clearTimeout(savePreloadDataTimeout);
    }
    savePreloadDataTimeout = setTimeout(() => {
      console.time('savePreloadData');
      localStorage.setItem('_require_preloadData', _preloadData(false));
      console.timeEnd('savePreloadData');
    }, autoPreload_enabled);
  }

  const _preloadData = prettify => {
    let available = [];
    let module_list = Object.keys(modules);
    const not_available = module => available.indexOf(module) === -1;

    // note that this is O(n^2) so if you have many, many modules it could get slow (but it should never be run by user)
    const no_deps = module => !dependency_map[module] || dependency_map[module].filter(not_available).length === 0;
    const waves = [];
    while(module_list.filter(not_available).length) {
      let wave = module_list.filter(not_available).filter(no_deps);
      waves.push(wave);
      if(wave.length === 0) {
        console.error('dependency deadlock');
        break;
      }
      available = available.concat(wave);
    }

    return JSON.stringify(waves, false, prettify ? 2 : false);
  };

  const _preload = waves => {
    function preload(waves) {
      if (waves.length) {
        const wave = waves.length ? waves.shift() : [];
        return Promise.all(wave.map(_lazy))
            .then(() => waves.length ? preload(waves) : null);
      }
    }
    return preload(waves);
  };

  const _autoPreload = (save_preload_data_throttle_ms) => {
    autoPreload_enabled = save_preload_data_throttle_ms || 2000;
    return new Promise(resolve => {
      const waves = localStorage.getItem('_require_preloadData');
      if (waves) {
        console.log('autoPreload data found');
        _preload(JSON.parse(waves)).then(resolve);
      } else {
        console.warn('no preload data found');
        resolve();
      }
    });
  };

  global.__d = define;
  global.require.report = () => {
    console.table(modules_loaded_synchronously);
  };
  global.require.data = () => {
    return {
      modules: Object.keys(modules),
      modules_loaded_synchronously,
      dependency_map
    };
  };
  global.require.preloadData = _preloadData;
  global.require.preload = _preload;
  global.require.autoPreload = _autoPreload;
  global.require.lazy = _lazy;
  global.require.viaTag = _viaTag;
  global.require.modules = () => modules;
}(this));
<commit_msg>Auto preload should recover from file deletions<commit_after>/**
# Require
Copyright ©2016-2017 Tonio Loewald

Simple implementation of CommonJS *require*.

Usage:

    const module = require('path/to/module.js');

Relative paths are supported within a file, so, assuming we're inside
`path/to/module.js` and `another_module.js` is also in `path/to/`:

    const another_module = require('./another_module.js');

If you don't want synchronous requires (and, you don't):

    require.lazy('path/to/module.js').then(module => {
      // you're good to go!
    });

And finally, if you need to use some primordial library that wants
to be global:

    require.viaTag('path/to/babylon.min.js').then(() => {
      const engine = new BABYLON.Engine(canvas, true);
      ...
    });

## Quick and Dirty Async Delivery

Naive use of require will result in console spam about how
synchronous XHR requests are bad. Rather than using a compile phase
to avoid them, you can do this in the console:

    require.preloadData();

This will produce an array of data (in essence the inferred dependency
graph unspooled as "waves" of modules that can be loaded asynchronously
in order to get everything asynchronously loaded in parallel) which you
can pass to:

    require.preload([ ... ]).then(() => {
      // write the code you would have written before,
      // everything will have loaded asynchronously!
    });

Right now, preloadData() tries to load everything in parallel
as quickly as it can (modulo dependencies), meaning that big modules
with no dependencies can be a bottleneck. If this every becomes a problem
for me (including if I get bothered by enough people), I will drill down and
try to even out the preload "waves".

### That was boring, let's do this all automatically!

    require.autoPreload().then(() => {
      // write the code you would have written before,
      // everything will have loaded asynchronously after the first time!
    });

The first time this runs you'll see all the sync loading warnings, and then
it just goes away. Sometimes a code change will cause the preload data to
be automatically updated. Now, this isn't quite as good as working perfectly the
first time, which would require doing this on the server. It would be pretty
easy to integrate this with your devops workflow if it came to that.

    require.autoPreload(10000).then(...); // you can change the throttle delay

Note that the storing of preloadData is throttled because calculating dependencies
could get expensive for sufficiently complex projects. It defaults to 2000ms which
should be fine for anything but truly byzantine sites. It takes < 1ms on a reasonably
modern device for the b8r demo page, but if I recall correctly calculating the
dependencies is O(n^2).
*/
/* global console */

(function(global) {
  'use strict';
  var modules = {};
  const noop = () => {};
  const dependency_map = {};
  const modules_loaded_synchronously = [];
  var autoPreload_enabled = 0; // also used to throttle saving of preload data

  function define(module_name, source_code) {
    var module = {};
    const path = module_name.split('/');
    path.pop();
    /* jshint evil:true */
    var factory = new Function(
        'module', 'require', source_code + `\n//# sourceURL=${module_name}`);
    /* jshint evil:false */
    const local_require = module => {
      if (module.substr(0, 2) === './') {
        module = path.join('/') + module.substr(1);
      } else if (module.substr(0, 3) === '../') {
        path.pop();
        module = path.join('/') + module.substr(2);
      }
      if (!dependency_map[module_name]) {
        dependency_map[module_name] = [];
      }
      if (dependency_map[module_name].indexOf(module) === -1) {
        dependency_map[module_name].push(module);
      }
      return global.require(module);
    };
    local_require.lazy = _lazy;
    factory(module, local_require);
    modules[module_name] = module;
    return module;
  }

  function _require(module_name) {
    if (!modules[module_name]) {
      var request = new XMLHttpRequest();
      modules_loaded_synchronously.push(module_name);
      console.warn(module_name, 'was loaded synchronously');
      if (autoPreload_enabled) {
        _savePreloadData();
      }
      request.open('GET', module_name, false);
      request.send(null);
      if (request.status === 200) {
        define(module_name, request.responseText);
      } else {
        console.error('could not load required module', module_name);
        return {};
      }
    }
    return modules[module_name].exports;
  }

  global.require = _require;

  global.require.isDefined = function(module_name) {
    return !!modules[module_name];
  };

  const _lazy = module_name => {
    return new Promise(function(resolve, reject) {
      if (!modules[module_name]) {
        var request = new XMLHttpRequest();
        request.open('GET', module_name, true);
        request.onload = function(data) {
          if (request.readyState === 4) {
            if (request.status === 200) {
              (resolve ||
               noop)(define(module_name, request.responseText).exports);
            } else {
              (reject || noop)(request, data);
            }
          }
        };
        request.onerror = function(data) {
          (reject || noop)(request, data);
        };
        request.send(null);
      } else {
        setTimeout(() => resolve && resolve(modules[module_name].exports));
      }
    });
  };

  const scriptTags = {};
  const _viaTag = script_path => {
    if(!scriptTags[script_path]) {
      scriptTags[script_path] = new Promise((resolve) => {
        const script = document.createElement('script');
        script.setAttribute('src', script_path);
        script.addEventListener('load', resolve);
        document.body.appendChild(script);
      });
    }
    return scriptTags[script_path];
  };

  var savePreloadDataTimeout = 0;
  const _savePreloadData = () => {
    if (savePreloadDataTimeout) {
      clearTimeout(savePreloadDataTimeout);
    }
    savePreloadDataTimeout = setTimeout(() => {
      console.time('savePreloadData');
      localStorage.setItem('_require_preloadData', _preloadData(false));
      console.timeEnd('savePreloadData');
    }, autoPreload_enabled);
  }

  const _preloadData = prettify => {
    let available = [];
    let module_list = Object.keys(modules);
    const not_available = module => available.indexOf(module) === -1;

    // note that this is O(n^2) so if you have many, many modules it could get slow (but it should never be run by user)
    const no_deps = module => !dependency_map[module] || dependency_map[module].filter(not_available).length === 0;
    const waves = [];
    while(module_list.filter(not_available).length) {
      let wave = module_list.filter(not_available).filter(no_deps);
      waves.push(wave);
      if(wave.length === 0) {
        console.error('dependency deadlock');
        break;
      }
      available = available.concat(wave);
    }

    return JSON.stringify(waves, false, prettify ? 2 : false);
  };

  const _preload = waves => {
    function preload(waves) {
      if (waves.length) {
        const wave = waves.length ? waves.shift() : [];
        return Promise.all(wave.map(_lazy))
            .then(() => waves.length ? preload(waves) : null)
            .catch(() => {
              console.error('auto preload failed');
              localStorage.removeItem('_require_preloadData');
              window.location.reload();
            });
      }
    }
    return preload(waves);
  };

  const _autoPreload = (save_preload_data_throttle_ms) => {
    autoPreload_enabled = save_preload_data_throttle_ms || 2000;
    return new Promise(resolve => {
      const waves = localStorage.getItem('_require_preloadData');
      if (waves) {
        console.log('autoPreload data found');
        _preload(JSON.parse(waves)).then(resolve);
      } else {
        console.warn('no preload data found');
        resolve();
      }
    });
  };

  global.__d = define;
  global.require.report = () => {
    console.table(modules_loaded_synchronously);
  };
  global.require.data = () => {
    return {
      modules: Object.keys(modules),
      modules_loaded_synchronously,
      dependency_map
    };
  };
  global.require.preloadData = _preloadData;
  global.require.preload = _preload;
  global.require.autoPreload = _autoPreload;
  global.require.lazy = _lazy;
  global.require.viaTag = _viaTag;
  global.require.modules = () => modules;
}(this));
<|endoftext|>


<commit_before>var fqdn = require("FQDN"),
    fs = require("fs"),
    discovery = require("ot-discovery"),
    config, lastUpdate, disco, lease, FQDN;

module.exports.init = function(server, options, done){
  config = options;

  disco = new discovery(config.host,
    {
      logger: {
        log: function(log){
          server.log(["discovery"], log);
        },
        error: function(err){
          server.log(["discovery", "error"], err);
        }
      }
    }
  );

  disco.connect(function(err, host, servers){
    if(err){
      throw err;
    }
    server.log(["discovery"], "discovery initialised");
    lastUpdate = new Date();

    disco.onUpdate(function(data){
      lastUpdate = new Date();
    });

    fqdn(function(err, res){
      if(err){
        throw err;
      }

      FQDN = res;
      done();
    });
  });
};

module.exports.announce = function(done){

  if(lease){
    return done();
  }

  disco.announce({
    serviceType: "location-api",
    serviceUri: "http://" + FQDN
  }, function(err, res){
    if(err){
      return done(err);
    }

    lease = res;
    done();
  });
};

module.exports.unannounce = function(done){

  if(!lease){
    return done();
  }

  disco.unannounce(lease, function(){
    lease = null;
    done();
  });
};

module.exports.lease = function(){ return lease; };
module.exports.lastUpdate = function(){ return lastUpdate; };
<commit_msg>use config for serviceType and allow https<commit_after>var fqdn = require("FQDN"),
    fs = require("fs"),
    discovery = require("ot-discovery"),
    config, lastUpdate, disco, lease, FQDN;

module.exports.init = function(server, options, done){
  config = options;

  disco = new discovery(config.host,
    {
      logger: {
        log: function(log){
          server.log(["discovery"], log);
        },
        error: function(err){
          server.log(["discovery", "error"], err);
        }
      }
    }
  );

  disco.connect(function(err, host, servers){
    if(err){
      throw err;
    }
    server.log(["discovery"], "discovery initialised");
    lastUpdate = new Date();

    disco.onUpdate(function(data){
      lastUpdate = new Date();
    });

    fqdn(function(err, res){
      if(err){
        throw err;
      }

      FQDN = res;
      done();
    });
  });
};

module.exports.announce = function(done){

  if(lease){
    return done();
  }

  disco.announce({
    serviceType: config.serviceType,
    serviceUri: (config.https ? "https": "http") + "://" + FQDN
  }, function(err, res){
    if(err){
      return done(err);
    }

    lease = res;
    done();
  });
};

module.exports.unannounce = function(done){

  if(!lease){
    return done();
  }

  disco.unannounce(lease, function(){
    lease = null;
    done();
  });
};

module.exports.lease = function(){ return lease; };
module.exports.lastUpdate = function(){ return lastUpdate; };
<|endoftext|>


<commit_before>"use strict";

const Promise = require('bluebird');
const request = Promise.promisifyAll(require("request"));
const cheerio = require('cheerio');
const Food = require("../models/food");
const Menu = require("../models/menu");
const config = require('../config');
const timeHelper = require('../helpers/timeHelper');

var MensaUrl = config.scraper.mensaUrl;
var UniwirtUrl = config.scraper.uniwirtUrl;
var MittagstischUrl = config.scraper.mittagstischUrl;
var PizzeriaUrl = config.scraper.unipizzeriaUrl;

function parseWeek(input, parseFunction) {
    var menus = [];
    for (let day = 0; day < 7; day++) {
        menus[day] = parseFunction(input, day);
    }

    return menus;
}

function getUniwirtWeekPlan() {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseWeek(body, parseUniwirt));
}

function getUniwirtPlan(day) {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseUniwirt(body, day));
}

function parseUniwirt(html, day) {
    var result = new Menu();

    var $ = cheerio.load(html);
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    if ($("#StandardWrapper").find(".col600 > .col360.noMargin > h3").html().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
	return result;
    }

    var $currentDayRows = $("#StandardWrapper").find(".col600 > .col360.noMargin").eq(dayInWeek).find('tr');

    $currentDayRows.each((index, item) => {
        var $tds = $(item).children();
        var name = $tds.eq(0).text();
        name = sanitizeName(name);
        var price = $tds.eq(2).text();

        if (contains(name, true, ["feiertag", "ruhetag", "wir machen pause"])) {
            result.closed = true;
        } else if (!price) {
            result.starters.push(new Food(name));
        } else {
            price = +price.substring(2).replace(',', '.');
            result.mains.push(new Food(name, price));
        }
    });

    return setErrorOnEmpty(result);
}

function getMensaWeekPlan() {
    return request.getAsync(MensaUrl)
        .then(res => res.body)
        .then(body => parseWeek(body, parseMensa));
}

function getMensaPlan(day) {
    return request.getAsync(MensaUrl)
        .then(res => res.body)
        .then(body => parseMensa(body, day));
}

function parseMensa(html, day) {
    var result = new Menu();
    var $ = cheerio.load(html);
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    if (dayInWeek > 4) {
        result.closed = true;
        return result;
    }

    var $weekDates = $('#days').find(".date");
    if ($weekDates.text().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
	return result;
    }

    $('.day-content br').replaceWith(' ');

    var $classic1 = $('.day-content #category133');
    var $classic2 = $('.day-content #category134');
    var $dailySpecial = $('.day-content #category247');

    var currentDay1 = $classic1.find('.category-content').eq(dayInWeek).find('p');
    var currentDay2 = $classic2.find('.category-content').eq(dayInWeek).find('p');
    var currentDaySpecial = $dailySpecial.find('.category-content').eq(dayInWeek).find('p');

    if (contains(currentDay1.text(), true, ["feiertag", "ruhetag"]) ||
        contains(currentDay2.text(), true, ["feiertag", "ruhetag"]) ||
        contains(currentDaySpecial.text(), true, ["feiertag", "ruhetag"])) {
        result.closed = true;
        return result;
    }

    currentDay1 = currentDay1.map((index, item) => $(item).text()).filter(isNotBlank).toArray();
    currentDay2 = currentDay2.map((index, item) => $(item).text()).filter(isNotBlank).toArray();
    currentDaySpecial = currentDaySpecial.map((index, item) => $(item).text()).filter(isNotBlank).toArray();

    result.mains = [];
    if (currentDay1.length) {
        result.mains.push(createFoodFromMenuSection($classic1, currentDay1, dayInWeek));
    }
    if (currentDay2.length) {
        result.mains.push(createFoodFromMenuSection($classic2, currentDay2, dayInWeek));
    }
    if (currentDaySpecial.length) {
        result.mains.push(createFoodFromMenuSection($dailySpecial, currentDaySpecial, dayInWeek));
    }
    return setErrorOnEmpty(result);
}

function getUniPizzeriaWeekPlan() {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => parseWeek(parseUniPizzeria(body), getUniPizzeriaDayPlan));
}

function getUniPizzeriaPlan(day) {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => {
            var weekMenu = parseUniPizzeria(body);
            return getUniPizzeriaDayPlan(weekMenu, day);
        });
}

function getUniPizzeriaDayPlan(weekMenu, day) {
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    var menu = new Menu();

    if (dayInWeek > 4) {
        let info = new Food("Kein Mittagsmenü.", null, true);
        menu.mains.push(info);
    } else if (dayInWeek < weekMenu.mains.length) {
        let combinedFood = weekMenu.mains[dayInWeek];

        //Handle holidays (no menu)
        if (contains(combinedFood.name, true, ["feiertag"])) {
            let info = new Food("Kein Mittagsmenü.", null, true);
            menu.mains.push(info);
        } else {

            let splitted = combinedFood.name.split("
");
            let starterExists = splitted.length > 1;

            if (starterExists) {
                //There is a starter
                let name = sanitizeName(splitted[0]);
                let starter = new Food(name);
                menu.starters.push(starter);
            }

            let i = starterExists ? 1 : 0;
            for (; i < splitted.length; i++) {
                let name = sanitizeName(splitted[i]);
                let main = new Food(name, combinedFood.price);
                menu.mains.push(main);
            }
        }
    }

    return setErrorOnEmpty(menu);
}

function parseUniPizzeria(html) {

    var result = new Menu();

    var $ = cheerio.load(html);
    var _days = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'je Mittagsteller € 7,80'];
    var _uniPizzeriaPrice = 7.80;

    var $menuContent = $('[itemprop="articleBody"]');

    if ($menuContent.find('p > strong').text().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
	return result;
    }

    var currentFood = null;
    $menuContent.find('p').each((index, item) => {

        var content = $(item).text().trim();

        // When the content of
 is a weekday, start new food
        if (_checkWeekday(content)) {
            if (currentFood != null) {
                result.mains.push(currentFood);
            }
            currentFood = new Food("", _uniPizzeriaPrice);
        }
        if (currentFood != null) {
            currentFood.name += _normalizeFood(content);
        }

    });

    if (currentFood != null && currentFood.name.length > 0) {
        result.mains.push(currentFood);
    }
    return setErrorOnEmpty(result);

    function _replaceWeekday(str) {
        for (var index in _days) {
            str = str.replace(_days[index], "");
        }
        return str;
    }

    function _checkWeekday(str) {
        var weekday = false;
        for (var index in _days) {
            if (str.indexOf(_days[index]) >= 0) {
                weekday = true;
            }
        }
        return weekday;
    }

    function _normalizeFood(str) {

        str = _replaceWeekday(str);
        str = str.replace(/\*+/, '
');

        return str;
    }

}

function createFoodFromMenuSection(section, menu, dayInWeek) {
    var price = null;
    var priceArray = section.eq(dayInWeek).find('.category-price').text().match(/€ (\S*)/);
    if (priceArray && priceArray.length) {
        price = priceArray[1];
        price = +price.replace(',', '.');
    }

    //isInfo <=> price could not get parsed (or is empty --> 0) and there is only one line of text
    var isInfo = (price == null || isNaN(price) || price === 0) && menu.length === 1;

    let name = sanitizeName(menu);
    return new Food(name, price, isInfo);
}

function getMittagstischWeekPlan() {
    return request.getAsync(MittagstischUrl)
        .then(res => res.body)
        .then(body => parseWeek(body, parseMittagstisch));
}

function getMittagstischPlan(day) {
    return request.getAsync(MittagstischUrl)
        .then(res => res.body)
        .then(body => parseMittagstisch(body, day));
}

function parseMittagstisch(body, day) {
    var foodMenu = new Menu();

    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    if (dayInWeek > 4) {
        foodMenu.closed = true;
        return foodMenu;
    }

    var $ = cheerio.load(body);

    if ($('.companyinfo').text().indexOf(timeHelper.getMondayDate()) == -1) {
        foodMenu.outdated = true;
	return foodMenu;
    }

    var dayStates = $(".resp-tabs-list").children();
    var currentDayClosed = false;
    var closedDaysMap = {};
    var closedCnt = 0;

    //Count number of closed days before each day (so we can adjust indices by this amount
    dayStates.each((index, item) => {
        closedDaysMap["" + index] = closedCnt;

        if (item.attribs.class.indexOf("closed") > -1) {
            closedCnt++;

            if (index === dayInWeek) {
                currentDayClosed = true;
            }
        }
    });

    if (currentDayClosed) {
        foodMenu.closed = true;
        return foodMenu;
    }

    var closedDays = closedDaysMap["" + dayInWeek];

    var plans = $(".resp-tabs-container .daydata");

    //Wochentag nicht verfügbar
    if (plans.length <= dayInWeek * 2 - closedDays) {
        return foodMenu;
    }

    //Menü
    var menu = plans.eq(dayInWeek * 2 - closedDays);
    var menuRows = menu.find("tr");
    var first = menuRows.first();

    var soupRows = first.nextUntil('.free', 'tr');
    if (!first.hasClass("free")) {
        soupRows.unshift(first);
    }

    soupRows.splice(0, 1);
    soupRows.each((index, item) => {
        var name = $(item).children().first().text();
        name = sanitizeName(name);
        foodMenu.starters.push(new Food(name));
    });

    //Hauptspeisen
    var mainsRows = soupRows.last().next().nextUntil('.free', 'tr');
    mainsRows.splice(0, 1);

    mainsRows.each((index, item) => {
        var name = $(item).children().first().text();
        name = sanitizeName(name);
        foodMenu.mains.push(new Food(name, 7.4));
    });

    //á la carte
    var aLaCarte = plans.eq(dayInWeek * 2 + 1 - closedDays);
    var aLaCarteRows = aLaCarte.find("tr");
    first = aLaCarteRows.first();

    var aLaCartes = first.nextUntil('.free', 'tr');
    if (!first.hasClass("free") && aLaCartes.unshift) {
        aLaCartes.unshift(first);
    }

    aLaCartes.splice(0, 1);
    aLaCartes.each((index, item) => {
        var entries = $(item).children();
        var name = entries.first().text();
        name = sanitizeName(name);
        var price = +entries.eq(1).text().replace(",", ".");
        foodMenu.alacarte.push(new Food(name, price));
    });

    return setErrorOnEmpty(foodMenu);
}

function isNotBlank(index, element) {
    return element.length !== 0 && element.trim();
}

function contains(str, ignoreCase, searches) {
    if (!str)
        return false;
    if (ignoreCase) {
        str = str.toLowerCase();
    }

    for (var i = 0; i < searches.length; i++) {
        var search = ignoreCase ? searches[i].toLowerCase() : searches[i];
        if (str.includes(search)) {
            return true;
        }
    }
    return false;
}

function setErrorOnEmpty(menu) {
    if (!(menu.starters.length || menu.mains.length || menu.alacarte.length)) {
        menu.error = true;
    }
    return menu;
}

function sanitizeName(val) {
    if (typeof val === "string") {
	      val = val.replace(/€\s[0-9](,|.)[0-9]+/, ""); // Replace '€ 00.00'
        val = val.replace(/^[1-9].\s/, ""); // Replace '1. ', '2. '
        val = val.replace(/^[,\.\-\\\? ]+/, "");
        val = val.replace(/[,\.\-\\\? ]+$/, "");
        return val;
    } else if (typeof val === "object" && val.length > 0) {
        for (let i = 0; i < val.length; i++) {
            val[i] = sanitizeName(val[i]);
        }
        return val;
    } else {
        return val;
    }
}

module.exports = {
    getUniwirtPlan: getUniwirtPlan,
    getUniwirtWeekPlan: getUniwirtWeekPlan,
    getMittagstischPlan: getMittagstischPlan,
    getMittagstischWeekPlan: getMittagstischWeekPlan,
    getMensaPlan: getMensaPlan,
    getMensaWeekPlan: getMensaWeekPlan,
    getUniPizzeriaPlan: getUniPizzeriaPlan,
    getUniPizzeriaWeekPlan: getUniPizzeriaWeekPlan
};
<commit_msg>Refactored Mensa Scraping (not yet splitting up the menu)<commit_after>"use strict";

const Promise = require('bluebird');
const request = Promise.promisifyAll(require("request"));
const cheerio = require('cheerio');
const Food = require("../models/food");
const Menu = require("../models/menu");
const config = require('../config');
const timeHelper = require('../helpers/timeHelper');

var MensaUrl = config.scraper.mensaUrl;
var UniwirtUrl = config.scraper.uniwirtUrl;
var MittagstischUrl = config.scraper.mittagstischUrl;
var PizzeriaUrl = config.scraper.unipizzeriaUrl;

function parseWeek(input, parseFunction) {
    var menus = [];
    for (let day = 0; day < 7; day++) {
        menus[day] = parseFunction(input, day);
    }

    return menus;
}

function getUniwirtWeekPlan() {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseWeek(body, parseUniwirt));
}

function getUniwirtPlan(day) {
    return request.getAsync(UniwirtUrl)
        .then(res => res.body)
        .then(body => parseUniwirt(body, day));
}

function parseUniwirt(html, day) {
    var result = new Menu();

    var $ = cheerio.load(html);
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    if ($("#StandardWrapper").find(".col600 > .col360.noMargin > h3").html().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
        return result;
    }

    var $currentDayRows = $("#StandardWrapper").find(".col600 > .col360.noMargin").eq(dayInWeek).find('tr');

    $currentDayRows.each((index, item) => {
        var $tds = $(item).children();
        var name = $tds.eq(0).text();
        name = sanitizeName(name);
        var price = $tds.eq(2).text();

        if (contains(name, true, ["feiertag", "ruhetag", "wir machen pause"])) {
            result.closed = true;
        } else if (!price) {
            result.starters.push(new Food(name));
        } else {
            price = +price.substring(2).replace(',', '.');
            result.mains.push(new Food(name, price));
        }
    });

    return setErrorOnEmpty(result);
}

function getMensaWeekPlan() {
    return request.getAsync(MensaUrl)
        .then(res => res.body)
        .then(body => parseMensa(body));
}

function parseMensa(html) {
    var result = new Array(7);

    let closedMenu = new Menu();
    closedMenu.closed = true;
    result[5] = result[6] = closedMenu;

    var $ = cheerio.load(html);

    let $weekDates = $('#days').find('.date');
    if ($weekDates.text().indexOf(timeHelper.getMondayDate()) == -1) {
        for (let i = 0; i < 5; i++) {
            let outdatedMenu = new Menu();
            outdatedMenu.outDated = true;
            result[i] = outdatedMenu;
        }
        return result;
    }

    //Get days
    let days = $("#days").find(".day");
    for (let dayInWeek = 0; dayInWeek < 5; dayInWeek++) {
        let menu = new Menu();
        result[dayInWeek] = menu;

        let day = days.eq(dayInWeek);

        let classic1Category = day.find('#category133');
        let classic2Category = day.find('#category134');
        let dailySpecialCategory = day.find('#category247');

        try {
            let classic1Food = createFoodFromMensaCategory(classic1Category);
            menu.mains.push(classic1Food);

            let classic2Food = createFoodFromMensaCategory(classic2Category);
            menu.mains.push(classic2Food);

            let dailySpecialFood = createFoodFromMensaCategory(dailySpecialCategory);
            menu.mains.push(dailySpecialFood);
        } catch (ex) {
            //Do not log error, as it is most likely to be a parsing error, which we do not want to fill the log file
            menu.error = true;
        }

        //Just to be sure
        setErrorOnEmpty(menu);
    }
    return result;
}

function createFoodFromMensaCategory(category) {
    var price = null;
    let priceArray = category.find('.category-price').text().match(/€ (\S*)/);
    if (priceArray && priceArray.length) {
        price = priceArray[1];
        price = +price.replace(',', '.');
    }

    let categoryContent = category.find(".category-content");

    let meals = categoryContent.find("p").eq(0);
    meals.find("br").replaceWith(' ');
    let name = [sanitizeName(meals.text())];

    //isInfo <=> price could not get parsed (or is empty --> 0) and there is only one line of text in .category-content
    var isInfo = (price == null || isNaN(price) || price === 0) && categoryContent.children().length === 1;

    return new Food(name, price, isInfo);
}

function getUniPizzeriaWeekPlan() {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => parseWeek(parseUniPizzeria(body), getUniPizzeriaDayPlan));
}

function getUniPizzeriaPlan(day) {
    return request.getAsync(PizzeriaUrl)
        .then(res => res.body)
        .then(body => {
            var weekMenu = parseUniPizzeria(body);
            return getUniPizzeriaDayPlan(weekMenu, day);
        });
}

function getUniPizzeriaDayPlan(weekMenu, day) {
    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    var menu = new Menu();

    if (dayInWeek > 4) {
        let info = new Food("Kein Mittagsmenü.", null, true);
        menu.mains.push(info);
    } else if (dayInWeek < weekMenu.mains.length) {
        let combinedFood = weekMenu.mains[dayInWeek];

        //Handle holidays (no menu)
        if (contains(combinedFood.name, true, ["feiertag"])) {
            let info = new Food("Kein Mittagsmenü.", null, true);
            menu.mains.push(info);
        } else {

            let splitted = combinedFood.name.split("
");
            let starterExists = splitted.length > 1;

            if (starterExists) {
                //There is a starter
                let name = sanitizeName(splitted[0]);
                let starter = new Food(name);
                menu.starters.push(starter);
            }

            let i = starterExists ? 1 : 0;
            for (; i < splitted.length; i++) {
                let name = sanitizeName(splitted[i]);
                let main = new Food(name, combinedFood.price);
                menu.mains.push(main);
            }
        }
    }

    return setErrorOnEmpty(menu);
}

function parseUniPizzeria(html) {

    var result = new Menu();

    var $ = cheerio.load(html);
    var _days = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'je Mittagsteller € 7,80'];
    var _uniPizzeriaPrice = 7.80;

    var $menuContent = $('[itemprop="articleBody"]');

    if ($menuContent.find('p > strong').text().indexOf(timeHelper.getMondayDate()) == -1) {
        result.outdated = true;
        return result;
    }

    var currentFood = null;
    $menuContent.find('p').each((index, item) => {

        var content = $(item).text().trim();

        // When the content of
 is a weekday, start new food
        if (_checkWeekday(content)) {
            if (currentFood != null) {
                result.mains.push(currentFood);
            }
            currentFood = new Food("", _uniPizzeriaPrice);
        }
        if (currentFood != null) {
            currentFood.name += _normalizeFood(content);
        }

    });

    if (currentFood != null && currentFood.name.length > 0) {
        result.mains.push(currentFood);
    }
    return setErrorOnEmpty(result);

    function _replaceWeekday(str) {
        for (var index in _days) {
            str = str.replace(_days[index], "");
        }
        return str;
    }

    function _checkWeekday(str) {
        var weekday = false;
        for (var index in _days) {
            if (str.indexOf(_days[index]) >= 0) {
                weekday = true;
            }
        }
        return weekday;
    }

    function _normalizeFood(str) {

        str = _replaceWeekday(str);
        str = str.replace(/\*+/, '
');

        return str;
    }

}

function getMittagstischWeekPlan() {
    return request.getAsync(MittagstischUrl)
        .then(res => res.body)
        .then(body => parseWeek(body, parseMittagstisch));
}

function getMittagstischPlan(day) {
    return request.getAsync(MittagstischUrl)
        .then(res => res.body)
        .then(body => parseMittagstisch(body, day));
}

function parseMittagstisch(body, day) {
    var foodMenu = new Menu();

    var dayInWeek;
    if (day === null || day === undefined) {
        dayInWeek = ((new Date()).getDay() + 6) % 7;
    } else {
        dayInWeek = day;
    }

    if (dayInWeek > 4) {
        foodMenu.closed = true;
        return foodMenu;
    }

    var $ = cheerio.load(body);

    if ($('.companyinfo').text().indexOf(timeHelper.getMondayDate()) == -1) {
        foodMenu.outdated = true;
        return foodMenu;
    }

    var dayStates = $(".resp-tabs-list").children();
    var currentDayClosed = false;
    var closedDaysMap = {};
    var closedCnt = 0;

    //Count number of closed days before each day (so we can adjust indices by this amount
    dayStates.each((index, item) => {
        closedDaysMap["" + index] = closedCnt;

        if (item.attribs.class.indexOf("closed") > -1) {
            closedCnt++;

            if (index === dayInWeek) {
                currentDayClosed = true;
            }
        }
    });

    if (currentDayClosed) {
        foodMenu.closed = true;
        return foodMenu;
    }

    var closedDays = closedDaysMap["" + dayInWeek];

    var plans = $(".resp-tabs-container .daydata");

    //Wochentag nicht verfügbar
    if (plans.length <= dayInWeek * 2 - closedDays) {
        return foodMenu;
    }

    //Menü
    var menu = plans.eq(dayInWeek * 2 - closedDays);
    var menuRows = menu.find("tr");
    var first = menuRows.first();

    var soupRows = first.nextUntil('.free', 'tr');
    if (!first.hasClass("free")) {
        soupRows.unshift(first);
    }

    soupRows.splice(0, 1);
    soupRows.each((index, item) => {
        var name = $(item).children().first().text();
        name = sanitizeName(name);
        foodMenu.starters.push(new Food(name));
    });

    //Hauptspeisen
    var mainsRows = soupRows.last().next().nextUntil('.free', 'tr');
    mainsRows.splice(0, 1);

    mainsRows.each((index, item) => {
        var name = $(item).children().first().text();
        name = sanitizeName(name);
        foodMenu.mains.push(new Food(name, 7.4));
    });

    //á la carte
    var aLaCarte = plans.eq(dayInWeek * 2 + 1 - closedDays);
    var aLaCarteRows = aLaCarte.find("tr");
    first = aLaCarteRows.first();

    var aLaCartes = first.nextUntil('.free', 'tr');
    if (!first.hasClass("free") && aLaCartes.unshift) {
        aLaCartes.unshift(first);
    }

    aLaCartes.splice(0, 1);
    aLaCartes.each((index, item) => {
        var entries = $(item).children();
        var name = entries.first().text();
        name = sanitizeName(name);
        var price = +entries.eq(1).text().replace(",", ".");
        foodMenu.alacarte.push(new Food(name, price));
    });

    return setErrorOnEmpty(foodMenu);
}

function isNotBlank(index, element) {
    return element.length !== 0 && element.trim();
}

function contains(str, ignoreCase, searches) {
    if (!str)
        return false;
    if (ignoreCase) {
        str = str.toLowerCase();
    }

    for (var i = 0; i < searches.length; i++) {
        var search = ignoreCase ? searches[i].toLowerCase() : searches[i];
        if (str.includes(search)) {
            return true;
        }
    }
    return false;
}

function setErrorOnEmpty(menu) {
    if (!(menu.starters.length || menu.mains.length || menu.alacarte.length)) {
        menu.error = true;
    }
    return menu;
}

function sanitizeName(val) {
    if (typeof val === "string") {
        val = val.replace(/€\s[0-9](,|.)[0-9]+/, ""); // Replace '€ 00.00'
        val = val.replace(/^[1-9].\s/, ""); // Replace '1. ', '2. '
        val = val.replace(/^[,\.\-\\\? ]+/, "");
        val = val.replace(/[,\.\-\\\? ]+$/, "");
        return val;
    } else if (typeof val === "object" && val.length > 0) {
        for (let i = 0; i < val.length; i++) {
            val[i] = sanitizeName(val[i]);
        }
        return val;
    } else {
        return val;
    }
}

module.exports = {
    getUniwirtPlan: getUniwirtPlan,
    getUniwirtWeekPlan: getUniwirtWeekPlan,
    getMittagstischPlan: getMittagstischPlan,
    getMittagstischWeekPlan: getMittagstischWeekPlan,
    getMensaWeekPlan: getMensaWeekPlan,
    getUniPizzeriaPlan: getUniPizzeriaPlan,
    getUniPizzeriaWeekPlan: getUniPizzeriaWeekPlan
};
<|endoftext|>


<commit_before>const express = require('express');
const bodyParser = require('body-parser');
const bot = require('../bot');
const refreshUser = require('./../lib/refresh');
const discourse_events = require('./../lib/discourse_events')
let app = express();
const path = require('path');
let ready = false;
const commands = require('./../commands');
app.use(bodyParser.json())
bot.on('ready', () => { ready = true; });
app.post('/push/user/:userId/updated', function (req, res) {
    //console.log(req);
    //console.log(req.params);
    if (!ready) return;
    console.log("Recieved Refresh request for <@" + req.params.userId + ">");
    refreshUser(bot.users.get(req.params.userId));
    res.send('updated');
    res.end();
});
app.post('/push/user/:userId/:userId2/transfer', function (req, res) {
    //console.log(req);
    //console.log(req.params);
    if (!ready) return;
    console.log("Recieved link transfer request from <@" + req.params.userId + "> to <@" + req.params.userId + ">")
    if (req.params.userId != req.params.userId2) {
        let guild = bot.guilds.get("184536578654339072");
        let m1 = guild.member(req.params.userId);
        let m2 = guild.member(req.params.userId2);
        m1.roles.map(async function (r) {
            if (r.name.includes("everyone")) return;
            m2.addRole(r);
        });

        refreshUser(m1.user);
        refreshUser(m2.user);
    }
    else
        refreshUser(bot.users.get(req.params.userId));
    res.send('updated');
    res.end();
});
app.get('/sql/messages', function (req, res) {
    var file = path.resolve(__dirname, '..', '..', 'dev.sqlite3');
    res.download(file);
    res.end();
});
app.get('/logs', function (req, res) {
    res.jsonp(commands.access_log);
    res.end();
});
app.post('/notify', function (req, res) {
    if (!ready) return;
    let event = req.headers['x-discourse-event-type'];
    let event_handler;
    if (!event) {
        return res.end(JSON.stringify(req.headers));
    }
    else if (event.toLowerCase() == 'post')
        event_handler = discourse_events.post;
    else if (event.toLowerCase() == 'user')
        event_handler = discourse_events.user;
    else if (event.toLowerCase() == 'ping')
        event_handler = discourse_events.ping;
    let body = req.body;
    if (typeof body === 'string')
        body = JSON.parse(body);
    body.base_url = req.headers['x-discourse-instance'];
    if (event_handler)
        event_handler(req.body);
    res.sendStatus(202);
    res.end();
});
app.get('/reverse/:id', async function (req, res) {
    if (!ready) return;
    console.log("Recieved reverse link request for <@" + req.params.id + ">")
    const user = bot.users.get(req.params.id);
    const guild = bot.guilds.get("184536578654339072");
    const member = guild.member(user);
    if (!member) {
        res.json({ is_admin: false, is_mod: false });
        return res.end();
    }
    const roles = member.roles.map((r) => {
        return r.id;
    })
    let result = {};
    let r = await refreshUser(user);
    if (!r) {
        res.json({ "error": "cant link" });
        return res.end();
    }
    result.name = r.username;
    result.is_mod = roles.includes("184676864630063104");
    result.is_admin = roles.includes("200849956796497920") || roles.includes("184684916833779712");
    res.json(result);
    res.end();
});
app.all('*', function (req, res) {
    console.log("Recieved request on an invalid route \n" + req.method + " " + req.originalUrl);
    res.end('{ "error": "Invalid route" }')
});
var listener = app.listen(8080, function () {
    console.log('API now running on port: ' + listener.address().port);
});

module.exports = app;
<commit_msg>fix mods<commit_after>const express = require('express');
const bodyParser = require('body-parser');
const bot = require('../bot');
const refreshUser = require('./../lib/refresh');
const discourse_events = require('./../lib/discourse_events')
let app = express();
const path = require('path');
let ready = false;
const commands = require('./../commands');
app.use(bodyParser.json())
bot.on('ready', () => { ready = true; });
app.post('/push/user/:userId/updated', function (req, res) {
    //console.log(req);
    //console.log(req.params);
    if (!ready) return;
    console.log("Recieved Refresh request for <@" + req.params.userId + ">");
    refreshUser(bot.users.get(req.params.userId));
    res.send('updated');
    res.end();
});
app.post('/push/user/:userId/:userId2/transfer', function (req, res) {
    //console.log(req);
    //console.log(req.params);
    if (!ready) return;
    console.log("Recieved link transfer request from <@" + req.params.userId + "> to <@" + req.params.userId + ">")
    if (req.params.userId != req.params.userId2) {
        let guild = bot.guilds.get("184536578654339072");
        let m1 = guild.member(req.params.userId);
        let m2 = guild.member(req.params.userId2);
        m1.roles.map(async function (r) {
            if (r.name.includes("everyone")) return;
            m2.addRole(r);
        });

        refreshUser(m1.user);
        refreshUser(m2.user);
    }
    else
        refreshUser(bot.users.get(req.params.userId));
    res.send('updated');
    res.end();
});
app.get('/sql/messages', function (req, res) {
    var file = path.resolve(__dirname, '..', '..', 'dev.sqlite3');
    res.download(file);
    res.end();
});
app.get('/logs', function (req, res) {
    res.jsonp(commands.access_log);
    res.end();
});
app.post('/notify', function (req, res) {
    if (!ready) return;
    let event = req.headers['x-discourse-event-type'];
    let event_handler;
    if (!event) {
        return res.end(JSON.stringify(req.headers));
    }
    else if (event.toLowerCase() == 'post')
        event_handler = discourse_events.post;
    else if (event.toLowerCase() == 'user')
        event_handler = discourse_events.user;
    else if (event.toLowerCase() == 'ping')
        event_handler = discourse_events.ping;
    let body = req.body;
    if (typeof body === 'string')
        body = JSON.parse(body);
    body.base_url = req.headers['x-discourse-instance'];
    if (event_handler)
        event_handler(req.body);
    res.sendStatus(202);
    res.end();
});
app.get('/reverse/:id', async function (req, res) {
    if (!ready) return;
    console.log("Recieved reverse link request for <@" + req.params.id + ">")
    const user = bot.users.get(req.params.id);
    const guild = bot.guilds.get("184536578654339072");
    const member = guild.member(user);
    if (!member) {
        res.json({ is_admin: false, is_mod: false });
        return res.end();
    }
    const roles = member.roles.map((r) => {
        return r.id;
    })
    let result = {};
    let r = await refreshUser(user);
    if (!r) {
        res.json({ "error": "cant link" });
        return res.end();
    }
    result.name = r.username;
    result.is_mod = roles.includes("352193753005424650");
    result.is_admin = roles.includes("353200495604793345") || roles.includes("184684916833779712");
    res.json(result);
    res.end();
});
app.all('*', function (req, res) {
    console.log("Recieved request on an invalid route \n" + req.method + " " + req.originalUrl);
    res.end('{ "error": "Invalid route" }')
});
var listener = app.listen(8080, function () {
    console.log('API now running on port: ' + listener.address().port);
});

module.exports = app;
<|endoftext|>


<commit_before>/**
 * bemhtml
 * =======
 *
 * Склеивает *bemhtml*-файлы по deps'ам, обрабатывает BEMHTML-транслятором, сохраняет (по умолчанию) в виде `?.bemhtml.js`.
 *
 * **Опции**
 *
 * * *String* **target** — Результирующий таргет. По умолчанию — `?.bemhtml.js`.
 * * *String* **filesTarget** — files-таргет, на основе которого получается список исходных файлов
 *   (его предоставляет технология `files`). По умолчанию — `?.files`.
 * * *String* **exportName** — Имя переменной-обработчика BEMHTML. По умолчанию — `'BEMHTML'`.
 * * *Boolean* **devMode** — Development-режим. По умолчанию — `true`.
 * * *Boolean* **cache** — Кеширование. По умолчанию — `true`.
 *
 * **Пример**
 *
 * ```javascript
 * nodeConfig.addTech(require('enb-bemhtml/techs/bemhtml'));
 * ```
 */
var Vow = require('vow'),
    vowFs = require('enb/lib/fs/async-fs'),
    BEMHTML = require('../lib/bemhtml');

module.exports = require('enb/lib/build-flow').create()
    .name('bemhtml')
    .target('target', '?.bemhtml.js')
    .defineOption('exportName', 'BEMHTML')
    .defineOption('devMode', true)
    .defineOption('cache', true)
    .useFileList('bemhtml')
    .builder(function(sourceFiles) {
        var _this = this;
        return Vow.all(sourceFiles.map(function(file) {
                return vowFs.read(file.fullname, 'utf8');
            }))
            .then(function(sources) {
                _this.node.getLogger().log('Calm down, OmetaJS is running...');
                var bemhtmlProcessor = BemhtmlProcessor.fork();
                return bemhtmlProcessor.process(sources.join('\n'), _this._devMode, _this._cache, _this._exportName, false).then(function(res) {
                    bemhtmlProcessor.dispose();
                    return res;
                });
            });
    })
    .createTech();

var BemhtmlProcessor = require('sibling').declare({
    process: function(source, devMode, cache, exportName, async) {
        return BEMHTML.translate(source, {
            devMode: devMode,
            cache: cache,
            exportName: exportName,
            async: async
        });
    }
});
<commit_msg>Added `_getOptions` method to `bemhtml` tech<commit_after>/**
 * bemhtml
 * =======
 *
 * Склеивает *bemhtml*-файлы по deps'ам, обрабатывает BEMHTML-транслятором, сохраняет (по умолчанию) в виде `?.bemhtml.js`.
 *
 * **Опции**
 *
 * * *String* **target** — Результирующий таргет. По умолчанию — `?.bemhtml.js`.
 * * *String* **filesTarget** — files-таргет, на основе которого получается список исходных файлов
 *   (его предоставляет технология `files`). По умолчанию — `?.files`.
 * * *String* **exportName** — Имя переменной-обработчика BEMHTML. По умолчанию — `'BEMHTML'`.
 * * *Boolean* **devMode** — Development-режим. По умолчанию — `true`.
 * * *Boolean* **cache** — Кеширование. По умолчанию — `true`.
 *
 * **Пример**
 *
 * ```javascript
 * nodeConfig.addTech(require('enb-bemhtml/techs/bemhtml'));
 * ```
 */
var Vow = require('vow'),
    vowFs = require('enb/lib/fs/async-fs'),
    BEMHTML = require('../lib/bemhtml');

module.exports = require('enb/lib/build-flow').create()
    .name('bemhtml')
    .target('target', '?.bemhtml.js')
    .defineOption('exportName', 'BEMHTML')
    .defineOption('devMode', true)
    .defineOption('cache', true)
    .useFileList('bemhtml')
    .builder(function(sourceFiles) {
        var _this = this;
        return Vow.all(sourceFiles.map(function(file) {
                return vowFs.read(file.fullname, 'utf8');
            }))
            .then(function(sources) {
                _this.node.getLogger().log('Calm down, OmetaJS is running...');
                var bemhtmlProcessor = BemhtmlProcessor.fork();
                return bemhtmlProcessor.process(sources.join('\n'), _this._getOptions()).then(function(res) {
                    bemhtmlProcessor.dispose();
                    return res;
                });
            });
    })
    .methods({
        _getOptions: function() {
            return {
                devMode: this._devMode,
                cache: this._cache,
                exportName: this._exportName,
                async: false
            };
        }
    })
    .createTech();

var BemhtmlProcessor = require('sibling').declare({
    process: function(source, options) {
        return BEMHTML.translate(source, options);
    }
});
<|endoftext|>


<commit_before>/*
 * grunt-responsive-videos
 * https://github.com/sjwilliams/grunt-responsive-videos
 *
 * Based on the very handy grunt-responsive-images
 * by andismith: https://github.com/andismith/grunt-responsive-images
 * and ffmpeg-node by xonecase: https://github.com/xonecas/ffmpeg-node
 *
 * Copyright (c) 2013 Josh Williams
 * Licensed under the MIT license.
 */

'use strict';

var _ = require('underscore'),
    async = require('async'),
    path = require('path'),
    ffmpeg = require('ffmpeg-node');

module.exports = function(grunt) {

    var DEFAULT_OPTIONS = {
        separator: '-',
        sizes: [{
            name: 'small',
            width: 320,
            poster: true
        },{
            name: 'large',
            width: 640,
            poster: true
        }
        ],
        encodes:[{
            webm: [
                {'-vcodec': 'libvpx'},
                {'-acodec': 'libvorbis'},
                {'-crf': '12'},
                {'-q:a': '100'},
                {'-threads': '0'}
            ],
            mp4: [
                {'-vcodec':'libx264'},
                {'-acodec': 'libfaac'},
                {'-pix_fmt': 'yuv420p'},
                {'-q:v': '4'},
                {'-q:a': '100'},
                {'-threads': '0'}
            ]
        }]
    };

    // check if there are any items in our array
    function isValidArray(obj) {
        return (_.isArray(obj) && obj.length > 0);
    }

    // check whether we've been given any valid size values
    function isValidSize(obj) {
        return (_.isNumber(obj.width) && obj.width%2===0);
    }

    // create a name to suffix to our file.
    function getName(name, width, separator) {

        // handle empty separator as no separator
        if (typeof separator === 'undefined') {
            separator = '';
        }

        if (name) {
            return separator + name;
        } else {
            return separator + width;
        }
    }

    // if a file exist, delete it.
    // otherwise ffmpeg will fail
    function deleteFile(path) {
        if (grunt.file.exists(path)) {
            console.log(path);
            grunt.file.delete(path);
        }
    }

    grunt.registerMultiTask('responsive_videos', 'Videos at various responsive sizes', function() {
        var that = this;

        // Merge task-specific and/or target-specific options with these defaults.
        var done = this.async();
        var series = [];
        var options = this.options(DEFAULT_OPTIONS);
        var sizeTally = {};  // track number of files per size
        var encodeTally = {}; // track number of files by codec
        var count = 0; // track total number of files

        if (!isValidArray(options.sizes)) {
            return grunt.fail.warn('No sizes have been defined.');
        }

        // build encode settings for each size
        options.sizes.forEach(function(size) {

            if (!isValidSize(size)) {
                return grunt.fail.warn('Width is invalid. It must be an integer and divisible by 2 (libx264 requirement)');
            }

            // create a name suffix for our image
            size.name = getName(size.name, size.width, options.separator);

            sizeTally[size.name] = 0;

            // build encode settings for each input file
            that.files.forEach(function(f) {
                var extName = path.extname(f.dest),
                    srcPath = f.src[0],
                    baseName = path.basename(srcPath, extName), // filename without extension
                    dirName = path.dirname(f.dest),
                    dstPath = path.join(dirName, baseName + size.name),
                    posterPath = dstPath + '.jpg';

                // more than 1 source.
                if (f.src.length > 1) {
                    return grunt.fail.warn('Unable to encode more than one video in compact or files object format.\n' +
                        'For multiple files please use the files array format.\nSee http://gruntjs.com/configuring-tasks');
                }

                // Make directory if it doesn't exist.
                if (!grunt.file.isDir(dirName)) {
                    grunt.file.mkdir(dirName);
                }

                // queue poster creation
                if (size.poster) {
                    deleteFile(posterPath);
                    series.push(function(callback){
                        var flags = [];
                        flags.push('-i', srcPath);
                        flags.push('-vframes', '1');
                        flags.push('-vf', 'scale='+size.width+':-1');
                        flags.push(posterPath);
                        ffmpeg.exec(flags, function() {
                            grunt.verbose.ok('Responsive Video: ' + srcPath + ' now ' + posterPath);
                            return callback();
                        });
                    });
                }

                // generate encode settings for each output encode type
                options.encodes.forEach(function(encodeSettings){
                    _.each(encodeSettings, function(codecSettings, codecName){
                        var outPath = dstPath + '.' + codecName;

                        if (encodeTally[codecName]) {
                            encodeTally[codecName]++;
                        } else {
                            encodeTally[codecName] = 1;
                        }

                        // build up flags array ffmpeg-node expects.
                        // ex: ['-i', srcPath, '-vcodec', 'libx264', '-acodec', 'libfaac', '-pix_fmt', 'yuv420p', '-q:v', '4',  '-q:a', '100', '-threads', '0', dstPath]
                        var flags = [];

                        // input file first
                        flags.push('-i', srcPath);

                        // encode settings next
                        _.each(codecSettings, function(codecSetting){
                            for (var key in codecSetting){
                                flags.push(key,codecSetting[key]);
                            }
                        });

                        // set size
                        flags.push('-vf', 'scale='+size.width+':-1');

                        // output file
                        flags.push(outPath);

                        // delete older versions
                        deleteFile(outPath);

                        // update tallies
                        sizeTally[size.name]++;
                        count++;

                        // queue encode jobs
                        series.push(function(callback){
                            ffmpeg.exec(flags, function() {
                                grunt.verbose.ok('Responsive Video: ' + srcPath + ' now ' + outPath);
                                return callback();
                            });
                        });

                    });

                    series.push(function(callback) {
                        if (sizeTally[size.name]) {
                            grunt.log.writeln('Created ' + sizeTally[size.name].toString().cyan + ' files for size ' + size.name);
                        }
                        return callback();
                    });
                });
            });
        });

        grunt.log.writeln('Starting ' + count.toString().cyan + ' encodes jobs.');

        // run encode jobs async
        async.series(series, done);
    });
};<commit_msg>added poster seek options<commit_after>/*
 * grunt-responsive-videos
 * https://github.com/sjwilliams/grunt-responsive-videos
 *
 * Based on the very handy grunt-responsive-images
 * by andismith: https://github.com/andismith/grunt-responsive-images
 * and ffmpeg-node by xonecase: https://github.com/xonecas/ffmpeg-node
 *
 * Copyright (c) 2013 Josh Williams
 * Licensed under the MIT license.
 */

'use strict';

var _ = require('underscore'),
    async = require('async'),
    path = require('path'),
    ffmpeg = require('ffmpeg-node');

module.exports = function(grunt) {

    var DEFAULT_OPTIONS = {
        separator: '-',
        sizes: [{
            name: 'small',
            width: 320,
            poster: true
        },{
            name: 'large',
            width: 640,
            poster: true
        }
        ],
        encodes:[{
            webm: [
                {'-vcodec': 'libvpx'},
                {'-acodec': 'libvorbis'},
                {'-crf': '12'},
                {'-q:a': '100'},
                {'-threads': '0'}
            ],
            mp4: [
                {'-vcodec':'libx264'},
                {'-acodec': 'libfaac'},
                {'-pix_fmt': 'yuv420p'},
                {'-q:v': '4'},
                {'-q:a': '100'},
                {'-threads': '0'}
            ]
        }]
    };

    // check if there are any items in our array
    function isValidArray(obj) {
        return (_.isArray(obj) && obj.length > 0);
    }

    // check whether we've been given any valid size values
    function isValidSize(obj) {
        return (_.isNumber(obj.width) && obj.width%2===0);
    }

    // create a name to suffix to our file.
    function getName(name, width, separator) {

        // handle empty separator as no separator
        if (typeof separator === 'undefined') {
            separator = '';
        }

        if (name) {
            return separator + name;
        } else {
            return separator + width;
        }
    }

    // if a file exist, delete it.
    // otherwise ffmpeg will fail
    function deleteFile(path) {
        if (grunt.file.exists(path)) {
            grunt.file.delete(path);
        }
    }

    // determine which type of poster to create for
    // the given video size and related options.
    //
    // Based on:
    // https://github.com/sjwilliams/grunt-responsive-videos/issues/3#issuecomment-31206990
    //
    // The default will be to use the first frame, but also options to 'fastseek'
    // and 'accurateseek' to a specific portion of the video
    function buildPosterFlags(size) {
        var posterBuilderOptions = {
            'boolean': function(){
                console.log('boolean');
            },
            'string': function(){
                console.log('string');
            },
            'object': function(){
                console.log('object');
            }
        };

        var posterConfigType = typeof size.poster;
        console.log(posterConfigType);

    }

    grunt.registerMultiTask('responsive_videos', 'Videos at various responsive sizes', function() {
        var that = this;

        // Merge task-specific and/or target-specific options with these defaults.
        var done = this.async();
        var series = [];
        var options = this.options(DEFAULT_OPTIONS);
        var sizeTally = {};  // track number of files per size
        var encodeTally = {}; // track number of files by codec
        var count = 0; // track total number of files

        if (!isValidArray(options.sizes)) {
            return grunt.fail.warn('No sizes have been defined.');
        }

        // build encode settings for each size
        options.sizes.forEach(function(size) {

            if (!isValidSize(size)) {
                return grunt.fail.warn('Width is invalid. It must be an integer and divisible by 2 (libx264 requirement)');
            }

            // create a name suffix for our image
            size.name = getName(size.name, size.width, options.separator);

            sizeTally[size.name] = 0;

            // build encode settings for each input file
            that.files.forEach(function(f) {
                var extName = path.extname(f.dest),
                    srcPath = f.src[0],
                    baseName = path.basename(srcPath, extName), // filename without extension
                    dirName = path.dirname(f.dest),
                    dstPath = path.join(dirName, baseName + size.name),
                    posterPath = dstPath + '.jpg';

                // more than 1 source.
                if (f.src.length > 1) {
                    return grunt.fail.warn('Unable to encode more than one video in compact or files object format.\n' +
                        'For multiple files please use the files array format.\nSee http://gruntjs.com/configuring-tasks');
                }

                // Make directory if it doesn't exist.
                if (!grunt.file.isDir(dirName)) {
                    grunt.file.mkdir(dirName);
                }

                // determine which type of poster to create for
                // the given video size and related options.
                //
                // The default will be to use the first frame, but also options to 'fastseek'
                // and 'accurateseek' to a specific portion of the video
                //
                // Based on:
                // https://github.com/sjwilliams/grunt-responsive-videos/issues/3#issuecomment-31206990
                // https://trac.ffmpeg.org/wiki/Seeking%20with%20FFmpeg
                if (size.poster) {
                    deleteFile(posterPath);

                    series.push(function(callback){
                        var flags = [];
                        var posterConfigType = typeof size.poster;
                        var seektime;

                        // accurateseek object contains an 'accurateseek' property
                        if (posterConfigType === 'object' && typeof size.poster.accurateseek === 'string') {

                            // -ss param after input
                            flags.push('-i', srcPath);
                            flags.push('-ss', size.poster.accurateseek);

                        // string is assumed to be format '00:02:00' and will fast seek.
                        // if object with fastseek propery, same settings
                        } else if (posterConfigType === 'string' || (posterConfigType === 'object' && typeof size.poster.fastseek === 'string')) {
                            seektime = (posterConfigType === 'string') ? size.poster : size.poster.fastseek;

                            // -ss param before input
                            flags.push('-ss', seektime);
                            flags.push('-i', srcPath);

                         // boolean true or something configured wrong.
                         // just grab the first frame. warn if misconfigured.
                        } else {
                            if (posterConfigType !== 'boolean') {
                                grunt.log.writeln('Poster option invalid. Using first frame.');
                            }
                            flags.push('-i', srcPath);
                        }

                        flags.push('-vframes', '1'); //grab only one frame
                        flags.push('-vf', 'scale='+size.width+':-1');
                        flags.push(posterPath);
                        ffmpeg.exec(flags, function() {
                            grunt.verbose.ok('Responsive Video: ' + srcPath + ' now ' + posterPath);
                            return callback();
                        });
                    });
                }

                // generate encode settings for each output encode type
                options.encodes.forEach(function(encodeSettings){
                    _.each(encodeSettings, function(codecSettings, codecName){
                        var outPath = dstPath + '.' + codecName;

                        if (encodeTally[codecName]) {
                            encodeTally[codecName]++;
                        } else {
                            encodeTally[codecName] = 1;
                        }

                        // build up flags array ffmpeg-node expects.
                        // ex: ['-i', srcPath, '-vcodec', 'libx264', '-acodec', 'libfaac', '-pix_fmt', 'yuv420p', '-q:v', '4',  '-q:a', '100', '-threads', '0', dstPath]
                        var flags = [];

                        // input file first
                        flags.push('-i', srcPath);

                        // encode settings next
                        _.each(codecSettings, function(codecSetting){
                            for (var key in codecSetting){
                                flags.push(key,codecSetting[key]);
                            }
                        });

                        // set size
                        flags.push('-vf', 'scale='+size.width+':-1');

                        // output file
                        flags.push(outPath);

                        // delete older versions
                        deleteFile(outPath);

                        // update tallies
                        sizeTally[size.name]++;
                        count++;

                        // queue encode jobs
                        series.push(function(callback){
                            ffmpeg.exec(flags, function() {
                                grunt.verbose.ok('Responsive Video: ' + srcPath + ' now ' + outPath);
                                return callback();
                            });
                        });

                    });

                    series.push(function(callback) {
                        if (sizeTally[size.name]) {
                            grunt.log.writeln('Created ' + sizeTally[size.name].toString().cyan + ' files for size ' + size.name);
                        }
                        return callback();
                    });
                });
            });
        });

        grunt.log.writeln('Starting ' + count.toString().cyan + ' encodes jobs.');

        // run encode jobs async
        async.series(series, done);
    });
};<|endoftext|>


<commit_before>/* global moment */
/* SBHS-Timetable-Node: Countdown and timetable all at once (NodeJS app).
 * Copyright (C) 2015 James Ye, Simon Shields
 *
 * This file is part of SBHS-Timetable-Node.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

var miniMode = window.innerWidth < 800;
var belltimes = undefined;
var timeSpentWaiting = moment();
var cachedCountdownEvent;
var countdownLabel = 'Loading...';
var inLabel = '';
var forcedDayOffset = 0; // brute-forcing public holidays and whatnot
var sbhsFailed = false;
var countdownToTheEnd = true; // only in pre-holiday mode
var _ctteCache = true; // internal use only

function getNextSchoolDay() {
	if (window.today) {
		var m = moment(window.today.date, "YYYY-MM-DD");
		if (m.hours(15).minutes(15).isAfter(moment())) {
			return m.startOf('day');
		}
	}
	var today = moment();
	var dow = today.days();
	var offset = forcedDayOffset;
	if (dow == 0) { // SUNDAY
		offset++;
	} else if (dow == 6) { // SATURDAY
		offset += 2;
	} else if (today.isAfter(moment().hours(15).minutes(15), 'minute')) {
		offset++;
		if (dow == 5) { // FRIDAY
			offset += 2;
		}
	}
	return today.add(offset, 'days').startOf('day');
}

function sbhsDown() {
	console.log('looks like SBHS is down.');
	$('#countdown-label').html('');
	$('#period-label').text('SBHS is taking too long!');
	$('#in-label').html('here, have a cute picture instead. reload at any time to try again. why does this happen?')
}

function getNextCountdownEvent() {
	if (!window.belltimes || !window.belltimes.status == 200) {
		if (timeSpentWaiting.diff(moment(), 'seconds') < -20 && !sbhsFailed) {
			sbhsFailed = true;
			setTimeout(sbhsDown,1000);
		}
		return timeSpentWaiting; // count up from page load time
	} else {
		if (cachedCountdownEvent && cachedCountdownEvent.isAfter(moment()) && _ctteCache == countdownToTheEnd) {
			return cachedCountdownEvent;
		}
		_ctteCache = countdownToTheEnd;
		var termEnd = moment(config.nextHolidayEvent.moment);
		if (countdownToTheEnd && moment().add(1, 'd').isAfter(termEnd) && moment().isBefore(termEnd)) {
			countdownLabel = 'School ends';
			inLabel = 'finallyin';
			$('#in-label,#countdown-label,#period-label').addClass('toggleable');
			hookToggleable();
			cachedCountdownEvent = termEnd;
			return termEnd;
		}
		if (moment().isAfter(termEnd)) {
			window.location = window.location; // reload
		}
		var i = 0;
		var now = moment();
		var nextSchoolDay = getNextSchoolDay();
		for (i = window.startIndex || 0; i < belltimes.bells.length; i++) { // loop over bells to find the next one
			var bell = belltimes.bells[i];
			var hm = bell.time.split(':');
			if (nextSchoolDay.hours(Number(hm[0])).minutes(Number(hm[1])).isAfter(now)) {
				inLabel = 'starts in';
				countdownLabel = bell.bell.replace('Roll Call', 'School Starts').replace('End of Day', 'School Ends');
				if (countdownLabel.indexOf('School') != -1) {
					inLabel = 'in';
				}
				if (window.today && /^\d/.test(bell.bell) && window.today.timetable) {
					// period - populate data from timetable
					if (bell.bell in today.timetable) {
						countdownLabel = today.timetable[bell.bell].fullName;
					} else {
						countdownLabel = 'Free';
					}
				} else if (/^\d/.test(bell.bell)) {
					countdownLabel = 'Period ' + bell.bell;
				}
				if (countdownLabel == 'Transition' || countdownLabel == 'Recess' || countdownLabel == 'Lunch 1') {
					inLabel = 'ends in';
					var next = belltimes.bells[i-1];
					if (window.today && window.today.timetable && /^\d/.test(next.bell)) {
						if (next.bell in today.timetable) {
							countdownLabel = today.timetable[next.bell].fullName;
						} else {
							countdownLabel = 'Free';
						}
					} else if (/^\d/.test(next.bell)) {
						countdownLabel = 'Period ' + next.bell;
					} else {
						countdownLabel = next.bell;
					}
				}
				/*if (countdownLabel.startsWith('Transition') || countdownLabel === 'Lunch 2' || countdownLabel.startsWith('Recess')) {
					inLabel = 'starts in';
					var next = belltimes.bells[i+1];
					if (window.today && /^\d/.test(next.bell)) {
						if (next.bell in today.timetable) {
							countdownLabel = today.timetable[next.bell].fullName;
						} else {
							countdownLabel = 'Free';
						}
					} else if (/^\d/.test(next.bell)) {
						countdownLabel = 'Period ' + next.bell;
					} else {
						countdownLabel = next.bell;
					}
				}*/
				cachedCountdownEvent = nextSchoolDay;
				return nextSchoolDay;
			}
		}
		return now;
	}
}

EventBus.on('bells', function(ev, bells) {
	window.belltimes = bells;
	if (window.belltimes.status == "Error") {
		if (window.today) {
			reloadBells();
			loadNotices();
			loadBarcodeNews();
		} else {
			forcedDayOffset++;
			if (forcedDayOffset > 10) {
				// cry
				sbhsDown();
			} else {
				reloadBells();
				loadNotices();
				loadBarcodeNews();
			}
		}
	}
	if (belltimes.bellsAltered) $('#top-line-notice').text('Bells changed: ' + belltimes.bellsAlteredReason);
	else $('#top-line-notice').text('');
});

function updateCountdown() {
	if (config.HOLIDAYS || sbhsFailed) return;
	$('#countdown-label').text(getNextCountdownEvent().fromNowCountdown());///*Math.abs(getNextCountdownEvent().diff(moment(), 'seconds')) + 's'*/);
	$('#in-label').html(inLabel);
	$('#period-label').text(countdownLabel);

}

function reloadBells() {
	$.getJSON('/api/belltimes?date=' + getNextSchoolDay().format('YYYY-MM-DD'), function(data) {
		EventBus.post('bells', data);
	});
}

function domReady() {
	if (document.readyState !== 'complete') return;
	if (!window.moment || !window.$ || !$.Velocity) {
		console.warn('MISSING SOME THINGS!');
		console.warn('this would go badly. so we won\'t let it go at all (▀̿Ĺ̯▀̿ ̿)');
		document.getElementById('period-label').innerHTML = 'Oops';
		document.getElementById('in-label').innerHTML = 'We couldn\'t load some things we need to run. Maybe try again?
or look at this picture!';
		document.getElementById('countdown-label').innerHTML = '';
		return;
	}
	window.belltimes = window.config.bells;
	$('#top-line-notice').text(belltimes.bellsAlteredReason);
	updateCountdown();
	reloadBells();
	// holidays
	if (config.HOLIDAYS) {
		$('#period-label,#countdown-label,.arrow,.sidebar').css({display: 'none'});
		$('#yt').css({display: 'block'}).html('');
		$('#in-label').html(config.holidayCfg.text);
		$('body').css({'background': config.holidayCfg.background});
	} else {
		loadBackgroundImage();
		if (window.config.loggedIn) {
			$('#login-status').html('Log out ');
		} else {
			$('#login-status').html('Log in ');
		}
		setInterval(updateCountdown, 1000);
	}
	attachAllTheThings();
	EventBus.post('pageload', {});
}

document.addEventListener('readystatechange', domReady);
<commit_msg>countdown: use changed belltimes straight away.<commit_after>/* global moment */
/* SBHS-Timetable-Node: Countdown and timetable all at once (NodeJS app).
 * Copyright (C) 2015 James Ye, Simon Shields
 *
 * This file is part of SBHS-Timetable-Node.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

var miniMode = window.innerWidth < 800;
var belltimes = undefined;
var timeSpentWaiting = moment();
var cachedCountdownEvent;
var countdownLabel = 'Loading...';
var inLabel = '';
var forcedDayOffset = 0; // brute-forcing public holidays and whatnot
var sbhsFailed = false;
var countdownToTheEnd = true; // only in pre-holiday mode
var _ctteCache = true; // internal use only

function getNextSchoolDay() {
	if (window.today) {
		var m = moment(window.today.date, "YYYY-MM-DD");
		if (m.hours(15).minutes(15).isAfter(moment())) {
			return m.startOf('day');
		}
	}
	var today = moment();
	var dow = today.days();
	var offset = forcedDayOffset;
	if (dow == 0) { // SUNDAY
		offset++;
	} else if (dow == 6) { // SATURDAY
		offset += 2;
	} else if (today.isAfter(moment().hours(15).minutes(15), 'minute')) {
		offset++;
		if (dow == 5) { // FRIDAY
			offset += 2;
		}
	}
	return today.add(offset, 'days').startOf('day');
}

function sbhsDown() {
	console.log('looks like SBHS is down.');
	$('#countdown-label').html('');
	$('#period-label').text('SBHS is taking too long!');
	$('#in-label').html('here, have a cute picture instead. reload at any time to try again. why does this happen?')
}

function getNextCountdownEvent() {
	if (!window.belltimes || !window.belltimes.status == 200) {
		if (timeSpentWaiting.diff(moment(), 'seconds') < -20 && !sbhsFailed) {
			sbhsFailed = true;
			setTimeout(sbhsDown,1000);
		}
		return timeSpentWaiting; // count up from page load time
	} else {
		if (cachedCountdownEvent && cachedCountdownEvent.isAfter(moment()) && _ctteCache == countdownToTheEnd) {
			return cachedCountdownEvent;
		}
		_ctteCache = countdownToTheEnd;
		var termEnd = moment(config.nextHolidayEvent.moment);
		if (countdownToTheEnd && moment().add(1, 'd').isAfter(termEnd) && moment().isBefore(termEnd)) {
			countdownLabel = 'School ends';
			inLabel = 'finallyin';
			$('#in-label,#countdown-label,#period-label').addClass('toggleable');
			hookToggleable();
			cachedCountdownEvent = termEnd;
			return termEnd;
		}
		if (moment().isAfter(termEnd)) {
			window.location = window.location; // reload
		}
		var i = 0;
		var now = moment();
		var nextSchoolDay = getNextSchoolDay();
		for (i = window.startIndex || 0; i < belltimes.bells.length; i++) { // loop over bells to find the next one
			var bell = belltimes.bells[i];
			var hm = bell.time.split(':');
			if (nextSchoolDay.hours(Number(hm[0])).minutes(Number(hm[1])).isAfter(now)) {
				inLabel = 'starts in';
				countdownLabel = bell.bell.replace('Roll Call', 'School Starts').replace('End of Day', 'School Ends');
				if (countdownLabel.indexOf('School') != -1) {
					inLabel = 'in';
				}
				if (window.today && /^\d/.test(bell.bell) && window.today.timetable) {
					// period - populate data from timetable
					if (bell.bell in today.timetable) {
						countdownLabel = today.timetable[bell.bell].fullName;
					} else {
						countdownLabel = 'Free';
					}
				} else if (/^\d/.test(bell.bell)) {
					countdownLabel = 'Period ' + bell.bell;
				}
				if (countdownLabel == 'Transition' || countdownLabel == 'Recess' || countdownLabel == 'Lunch 1') {
					inLabel = 'ends in';
					var next = belltimes.bells[i-1];
					if (window.today && window.today.timetable && /^\d/.test(next.bell)) {
						if (next.bell in today.timetable) {
							countdownLabel = today.timetable[next.bell].fullName;
						} else {
							countdownLabel = 'Free';
						}
					} else if (/^\d/.test(next.bell)) {
						countdownLabel = 'Period ' + next.bell;
					} else {
						countdownLabel = next.bell;
					}
				}
				/*if (countdownLabel.startsWith('Transition') || countdownLabel === 'Lunch 2' || countdownLabel.startsWith('Recess')) {
					inLabel = 'starts in';
					var next = belltimes.bells[i+1];
					if (window.today && /^\d/.test(next.bell)) {
						if (next.bell in today.timetable) {
							countdownLabel = today.timetable[next.bell].fullName;
						} else {
							countdownLabel = 'Free';
						}
					} else if (/^\d/.test(next.bell)) {
						countdownLabel = 'Period ' + next.bell;
					} else {
						countdownLabel = next.bell;
					}
				}*/
				cachedCountdownEvent = nextSchoolDay;
				return nextSchoolDay;
			}
		}
		return now;
	}
}

EventBus.on('bells', function(ev, bells) {
	window.belltimes = bells;
	if (window.belltimes.status == "Error") {
		if (window.today) {
			reloadBells();
			loadNotices();
			loadBarcodeNews();
		} else {
			forcedDayOffset++;
			if (forcedDayOffset > 10) {
				// cry
				sbhsDown();
			} else {
				reloadBells();
				loadNotices();
				loadBarcodeNews();
				return;
			}
		}
	}
	cachedCountdownEvent = null;
	if (belltimes.bellsAltered) $('#top-line-notice').text('Bells changed: ' + belltimes.bellsAlteredReason);
	else $('#top-line-notice').text('');
});

function updateCountdown() {
	if (config.HOLIDAYS || sbhsFailed) return;
	$('#countdown-label').text(getNextCountdownEvent().fromNowCountdown());///*Math.abs(getNextCountdownEvent().diff(moment(), 'seconds')) + 's'*/);
	$('#in-label').html(inLabel);
	$('#period-label').text(countdownLabel);

}

function reloadBells() {
	$.getJSON('/api/belltimes?date=' + getNextSchoolDay().format('YYYY-MM-DD'), function(data) {
		EventBus.post('bells', data);
	});
}

function domReady() {
	if (document.readyState !== 'complete') return;
	if (!window.moment || !window.$ || !$.Velocity) {
		console.warn('MISSING SOME THINGS!');
		console.warn('this would go badly. so we won\'t let it go at all (▀̿Ĺ̯▀̿ ̿)');
		document.getElementById('period-label').innerHTML = 'Oops';
		document.getElementById('in-label').innerHTML = 'We couldn\'t load some things we need to run. Maybe try again?
or look at this picture!';
		document.getElementById('countdown-label').innerHTML = '';
		return;
	}
	window.belltimes = window.config.bells;
	$('#top-line-notice').text(belltimes.bellsAlteredReason);
	updateCountdown();
	reloadBells();
	// holidays
	if (config.HOLIDAYS) {
		$('#period-label,#countdown-label,.arrow,.sidebar').css({display: 'none'});
		$('#yt').css({display: 'block'}).html('');
		$('#in-label').html(config.holidayCfg.text);
		$('body').css({'background': config.holidayCfg.background});
	} else {
		loadBackgroundImage();
		if (window.config.loggedIn) {
			$('#login-status').html('Log out ');
		} else {
			$('#login-status').html('Log in ');
		}
		setInterval(updateCountdown, 1000);
	}
	attachAllTheThings();
	EventBus.post('pageload', {});
}

document.addEventListener('readystatechange', domReady);
<|endoftext|>