<commit_before>// Requires
var TelegramBot = require('node-telegram-bot-api');
var bing        = require('bing-translate');

// Token
var token = ''; // Cambiar el token por el token de SakuraTranslate
bing.init({
        client_id: 'SakuraTranslate_bot',
        client_secret: '' // Token secreto de Sakura en Bing Translate
       });

// Inicialización
var sakura = new TelegramBot(token, {polling: true});
console.log("Sakura-chan está funcionando!");
var jap2esp = 0; // Contador de traducciones japones-español
var esp2jap = 0; // Contador de traducciones español-japones

// Japonés a Español
sakura.onText(/\/jp (.+)/, function (msg, match) {
  var fromId = msg.from.id;
  var input  = match[1];
  bing.translate(input, 'ja', 'es', function(err, res) { sakura.sendMessage(fromId, "La traducción para '" + input + "' es: " + res.translated_text) });
  jap2esp++;
});

// Español a Japonés
sakura.onText(/\/esp (.+)/, function (msg, match) {
  var fromId = msg.from.id;
  var input  = match[1];
  bing.translate(input, 'es', 'ja', function(err, res) { sakura.sendMessage(fromId, "La traducción para '" + input + "' es: " + res.translated_text) });
  esp2jap++;
});

// Repo
sakura.onText(/\/repo/, function (msg) {
  var fromId = msg.from.id;
  sakura.sendMessage(fromId, "https://github.com/JuanjoSalvador/SakuraChan");
});
<commit_msg>Añadido comando LIKE y reparado el envío de traducciones<commit_after>// Requires
var TelegramBot = require('node-telegram-bot-api');

// Token
var token = ''; // Cambiar el token por el token de SakuraTranslate
var bt = require('bing-translate').init({
                                        client_id: 'SakuraTranslate_bot',
                                        client_secret: '' // Token secreto de Sakura en Bing Translate
                                      });

// Inicialización
var sakura = new TelegramBot(token, {polling: true});
console.log("Sakura-chan está funcionando!");

// Start
sakura.onText(/\/start/, function (msg) {
  var fromId = msg.from.id;
  sakura.sendMessage(fromId, "Konnichiwa!");
});

// Japonés a Español
sakura.onText(/\/jp (.+)/, function (msg, match) {
  console.log("Mensaje enviado: ", msg.text); // Debug
  var fromId = msg.from.id;
  var input  = match[1];
  bt.translate(input, 'ja', 'es', function(err, res) { sakura.sendMessage(fromId, "'" + input + "', traducido, significa '" + res.translated_text + "'."); });
});

// Español a Japonés
sakura.onText(/\/esp (.+)/, function (msg, match) {
  console.log("Mensaje enviado: ", msg.text); // Debug
  var fromId = msg.from.id;
  var input  = match[1];
  bt.translate(input, 'es', 'ja', function(err, res) { sakura.sendMessage(fromId, "En japonés, '" + input + "' se escribe " + res.translated_text) });
});

// Repo
sakura.onText(/\/repo/, function (msg) {
  var fromId = msg.from.id;
  sakura.sendMessage(fromId, "https://github.com/JuanjoSalvador/SakuraChan");
});

// Like
sakura.onText(/\/like/, function (msg) {
  var fromId = msg.from.id;
  sakura.sendMessage(fromId, "¡Gracias " + msg.from.first_name + "! ¡A Sakura-chan también le gustas tu!");
});<|endoftext|>


<commit_before>module.exports = function Player() {

  var demo_module = {
    name: 'ok',
    data: [{
      interval: 1500,
      data: [1, 0, 0, 0, 0, 0, 0, 1]
    }, {
      data: [0, 0, 0, 0, 0, 0, 1, 0]
    }, {
      data: [0, 0, 0, 0, 0, 0, 1, 1]
    }, {
      interval: 100,
      data: [0, 0, 0, 0, 0, 1, 0, 0]
    }, {
      data: [0, 0, 0, 0, 0, 1, 0, 1]
    }, {
      data: [0, 0, 0, 0, 0, 1, 1, 0]
    }, {
      data: [0, 0, 0, 0, 0, 1, 1, 1]
    }, {
      data: [0, 0, 0, 0, 1, 0, 0, 0]
    }, {
      data: [0, 0, 0, 0, 1, 0, 0, 1]
    }]
  }

  var current_module
  var current_pattern
  var current_interval = 500 // ms
  var current_pattern_index = 0
  var sockets = []
  var gpio
  var pins = []

  function set_module(module){
    current_module = module
    set_pattern(current_module)
  }

  function set_pattern(module) {
    current_pattern = module.data
  }

  function save_pattern_to_db() {

  }

  function get_pattern_from_db(id) {

  }

  function tick() {

    if (current_pattern_index >= current_pattern.length) {
      current_pattern_index = 0
    }

    var p = current_pattern[current_pattern_index]
    if (p.interval !== undefined) {
      current_interval = p.interval
    }
    p.data.forEach(function (v, i) {
      if(v === 0){
        set_pin(i, false)
      } else {
        set_pin(i, true)
      }

    })

    console.log(p.data)

    sockets.forEach(function (s) {
      s.emit('current_index', {
        value: current_pattern_index
      })
    })

    current_pattern_index += 1

    setTimeout(tick, current_interval)

  }

  function start() {
    set_module(demo_module)
    tick()
  }

  function set_pin(pin, v) {
    // gpio[pin].set(v)
    if(gpio !== undefined){
      if(pins[pin] !== undefined){
        gpio.write(pins[pin], v)
      }
    }
  }

  return {
    get_pattern_from_db: get_pattern_from_db,
    save_pattern: save_pattern_to_db,
    start: start,
    tick: tick,
    set_pattern: set_pattern,
    set_module: set_module,
    current_pattern: function () {
      return current_pattern
    },
    current_module: function(){
      return current_module
    },
    set_sockets: function (s) {
      sockets = s
    },
    current_pattern_index: function(){
      return current_pattern_index
    },
    set_gpio: function(g,p){
      gpio = g
      pins = p
    }
  }

}
<commit_msg>gpio added<commit_after>module.exports = function Player() {

  var demo_module = {
    name: 'ok',
    data: [{
      interval: 1500,
      data: [1, 0, 0, 0, 0, 0, 0, 1]
    }, {
      data: [0, 1, 0, 0, 0, 0, 1, 0]
    }, {
      data: [0, 0, 1, 0, 0, 0, 1, 1]
    }, {
      interval: 100,
      data: [0, 0, 0, 1, 0, 1, 0, 0]
    }, {
      data: [1, 1, 1, 1, 0, 1, 0, 1]
    }, {
      data: [1, 0, 0, 0, 0, 1, 1, 0]
    }, {
      data: [1, 0, 1, 0, 0, 1, 1, 1]
    }, {
      data: [0, 0, 1, 1, 1, 0, 0, 0]
    }, {
      data: [0, 1, 1, 0, 1, 0, 0, 1]
    }]
  }

  var current_module
  var current_pattern
  var current_interval = 500 // ms
  var current_pattern_index = 0
  var sockets = []
  var gpio
  var pins = []

  function set_module(module){
    current_module = module
    set_pattern(current_module)
  }

  function set_pattern(module) {
    current_pattern = module.data
  }

  function save_pattern_to_db() {

  }

  function get_pattern_from_db(id) {

  }

  function tick() {

    if (current_pattern_index >= current_pattern.length) {
      current_pattern_index = 0
    }

    var p = current_pattern[current_pattern_index]
    if (p.interval !== undefined) {
      current_interval = p.interval
    }
    p.data.forEach(function (v, i) {
      if(v === 0){
        set_pin(i, true)
      } else {
        set_pin(i, false)
      }

    })

    console.log(p.data)

    sockets.forEach(function (s) {
      s.emit('current_index', {
        value: current_pattern_index
      })
    })

    current_pattern_index += 1

    setTimeout(tick, current_interval)

  }

  function start() {
    set_module(demo_module)
    tick()
  }

  function set_pin(pin, v) {
    // gpio[pin].set(v)
    if(gpio !== undefined){
      if(pins[pin] !== undefined){
        gpio.write(pins[pin], v)
      }
    }
  }

  return {
    get_pattern_from_db: get_pattern_from_db,
    save_pattern: save_pattern_to_db,
    start: start,
    tick: tick,
    set_pattern: set_pattern,
    set_module: set_module,
    current_pattern: function () {
      return current_pattern
    },
    current_module: function(){
      return current_module
    },
    set_sockets: function (s) {
      sockets = s
    },
    current_pattern_index: function(){
      return current_pattern_index
    },
    set_gpio: function(g,p){
      gpio = g
      pins = p
    }
  }

}
<|endoftext|>


<commit_before>import { inject as service } from '@ember/service';
import Component from '@ember/component';
import layout from '../templates/components/cs-version-control';
import { task } from 'ember-concurrency';
import { modelType } from '@cardstack/rendering/helpers/cs-model-type';
import { defaultBranch } from '@cardstack/plugin-utils/environment';
import { computed } from "@ember/object";
import { or } from '@ember/object/computed';

export default Component.extend({
  layout,
  tagName: '',
  opened: true,
  animationRules,
  "on-error": function() {},
  resourceMetadata: service(),
  store: service(),
  router: service(),
  data: service('cardstack-data'),
  tools: service('cardstack-tools'),

  modelMeta: computed('model', function() {
    return this.get('resourceMetadata').read(this.get('model'));
  }),

  modelType: computed('model', function() {
    return modelType(this.get('model'));
  }),

  onMaster: computed('modelMeta.branch', function() {
    let branch = this.get('modelMeta').branch;
    return branch == null || branch === defaultBranch;
  }),

  upstreamMeta: computed('upstreamModel', function() {
    let upstream = this.get('upstreamModel');
    if (upstream) {
      return this.get('resourceMetadata').read(upstream);
    }
  }),

  fetchUpstreamModel: task(function * () {
    this.set('upstreamModel', null);
    if (this.get('onMaster')) {
      // Nothing to do, we're already on the default branch, so there
      // is no model more upstream than ours.
      return;
    }
    let model = this.get('model');
    let type = modelType(model);
    let id = model.get('id');
    if (id == null) {
      // Nothing to do, we don't have an id yet. It's vanishingly
      // unlikely that we're about to collide with something on
      // upstream anyway.
      return;
    }
    try {
      let upstreamModel = yield this.get('store').findRecord('cardstack-generic', `master/${type}/${id}`);
      this.set('upstreamModel', upstreamModel);
    } catch (err) {
      if (err.isAdapterError && err.errors && err.errors.length > 0 && err.errors[0].code === 404) {
        // There's no upstream model, that's OK.
        return;
      }
      throw err;
    }
  }).observes('modelMeta.branch', 'model.id').on('init'),

  // this describes the state of our model relative to its branch. So
  // "saved" here means it has been saved to its branch, etc.
  modificationState: computed('model.isNew', 'anythingDirty', function() {
    if (this.get('model.isNew')) {
      return "new";
    } else  if (this.get('anythingDirty')) {
      return "changed";
    } else {
      return "saved";
    }
  }),

  // this describes the state of our model relative to its value on
  // the default branch.
  upstreamState: computed('upstreamMeta.hash', 'modelMeta.hash', 'onMaster', 'fetchUpstreamModel.isRunning', function() {
    if (this.get('onMaster')) {
      return 'self';
    }

    if (this.get('fetchUpstreamModel.isRunning')) {
      return 'pending';
    }

    let upstreamMeta = this.get('upstreamMeta');
    if (!upstreamMeta) {
      return 'created';
    }

    let meta = this.get('modelMeta');
    if (meta.hash === upstreamMeta.hash) {
      return 'same';
    } else {
      return 'different';
    }

  }),

  title: computed('modificationState', 'upstreamState', function() {
    switch(this.get('modificationState')) {
    case 'new':
      return 'Drafted'
    case 'changed':
      return 'Changed';
    case 'saved':
      switch(this.get('upstreamState')) {
      case 'pending':
        return '...';
      case 'self':
        return 'Live';
      case 'created':
      case 'different':
      case 'same':
        return 'Preview';
      }
    }
  }),

  disabled: computed.equal('modificationState', 'saved'),

  currentState: computed('modificationState', 'upstreamState', function() {
    let modificationState = this.get('modificationState');
    let upstreamState = this.get('upstreamState');

    switch (modificationState) {
      case 'new':
        return 'draft'
      case 'changed':
        return 'edited';
      case 'saved':
        if (upstreamState === 'self') return 'published';
        else return 'saved';
      default:
        return;
    }
  }),

  // hasDirtyFields comes from the ember-data-relationship-tracker
  // addon, if it's available. It's fine if it's not since the value
  // will default to false, you just don't get relationship dirty tracking
  anythingDirty: or('model.{hasDirtyFields,hasDirtyOwned}'),

  anythingPending: or('model.isNew', 'anythingDirty'),

  update: task(function * () {
    let model = this.get('model');
    let errors = yield this.get('data').validate(model);
    if (Object.keys(errors).length > 0) {
      return this['on-error'](errors);
    }
    yield model.save();
  }).keepLatest(),

  cancel: task(function * () {
    this.get('tools').setEditing(false);

    if (this.get('anythingDirty')) {
      let model = this.get('model');

      model.rollbackAttributes();
      model.rollbackRelationships();
      model.reload(); // this is to rollback attributes that are arrays

      if (model.dirtyTrackingRelationNames) {
        let relationNames = Object.keys(model.dirtyTrackingRelationNames);
        relationNames.forEach(relation => {
          let relationItems = model.get(relation);
          relationItems.forEach(item => {
            item.rollbackAttributes();
            item.rollbackRelationships();
          });
        });
      }
    }
  }),

  delete: task(function * () {
    yield this.get('model').destroyRecord();

    this.get('router').transitionTo('application');
  }),

  actions: {
    open() {
      this.set('opened', true);
    },
    close() {
      this.set('opened', false);
    }
  }
});

function animationRules() {
  this.transition(
    this.fromValue(false),
    this.toValue(true),
    this.use('to-down', { duration: 250 }),
    this.reverse('to-up', { duration: 250 })
  );
}
<commit_msg>Add yield<commit_after>import { inject as service } from '@ember/service';
import Component from '@ember/component';
import layout from '../templates/components/cs-version-control';
import { task } from 'ember-concurrency';
import { modelType } from '@cardstack/rendering/helpers/cs-model-type';
import { defaultBranch } from '@cardstack/plugin-utils/environment';
import { computed } from "@ember/object";
import { or } from '@ember/object/computed';

export default Component.extend({
  layout,
  tagName: '',
  opened: true,
  animationRules,
  "on-error": function() {},
  resourceMetadata: service(),
  store: service(),
  router: service(),
  data: service('cardstack-data'),
  tools: service('cardstack-tools'),

  modelMeta: computed('model', function() {
    return this.get('resourceMetadata').read(this.get('model'));
  }),

  modelType: computed('model', function() {
    return modelType(this.get('model'));
  }),

  onMaster: computed('modelMeta.branch', function() {
    let branch = this.get('modelMeta').branch;
    return branch == null || branch === defaultBranch;
  }),

  upstreamMeta: computed('upstreamModel', function() {
    let upstream = this.get('upstreamModel');
    if (upstream) {
      return this.get('resourceMetadata').read(upstream);
    }
  }),

  fetchUpstreamModel: task(function * () {
    this.set('upstreamModel', null);
    if (this.get('onMaster')) {
      // Nothing to do, we're already on the default branch, so there
      // is no model more upstream than ours.
      return;
    }
    let model = this.get('model');
    let type = modelType(model);
    let id = model.get('id');
    if (id == null) {
      // Nothing to do, we don't have an id yet. It's vanishingly
      // unlikely that we're about to collide with something on
      // upstream anyway.
      return;
    }
    try {
      let upstreamModel = yield this.get('store').findRecord('cardstack-generic', `master/${type}/${id}`);
      this.set('upstreamModel', upstreamModel);
    } catch (err) {
      if (err.isAdapterError && err.errors && err.errors.length > 0 && err.errors[0].code === 404) {
        // There's no upstream model, that's OK.
        return;
      }
      throw err;
    }
  }).observes('modelMeta.branch', 'model.id').on('init'),

  // this describes the state of our model relative to its branch. So
  // "saved" here means it has been saved to its branch, etc.
  modificationState: computed('model.isNew', 'anythingDirty', function() {
    if (this.get('model.isNew')) {
      return "new";
    } else  if (this.get('anythingDirty')) {
      return "changed";
    } else {
      return "saved";
    }
  }),

  // this describes the state of our model relative to its value on
  // the default branch.
  upstreamState: computed('upstreamMeta.hash', 'modelMeta.hash', 'onMaster', 'fetchUpstreamModel.isRunning', function() {
    if (this.get('onMaster')) {
      return 'self';
    }

    if (this.get('fetchUpstreamModel.isRunning')) {
      return 'pending';
    }

    let upstreamMeta = this.get('upstreamMeta');
    if (!upstreamMeta) {
      return 'created';
    }

    let meta = this.get('modelMeta');
    if (meta.hash === upstreamMeta.hash) {
      return 'same';
    } else {
      return 'different';
    }

  }),

  title: computed('modificationState', 'upstreamState', function() {
    switch(this.get('modificationState')) {
    case 'new':
      return 'Drafted'
    case 'changed':
      return 'Changed';
    case 'saved':
      switch(this.get('upstreamState')) {
      case 'pending':
        return '...';
      case 'self':
        return 'Live';
      case 'created':
      case 'different':
      case 'same':
        return 'Preview';
      }
    }
  }),

  disabled: computed.equal('modificationState', 'saved'),

  currentState: computed('modificationState', 'upstreamState', function() {
    let modificationState = this.get('modificationState');
    let upstreamState = this.get('upstreamState');

    switch (modificationState) {
      case 'new':
        return 'draft'
      case 'changed':
        return 'edited';
      case 'saved':
        if (upstreamState === 'self') return 'published';
        else return 'saved';
      default:
        return;
    }
  }),

  // hasDirtyFields comes from the ember-data-relationship-tracker
  // addon, if it's available. It's fine if it's not since the value
  // will default to false, you just don't get relationship dirty tracking
  anythingDirty: or('model.{hasDirtyFields,hasDirtyOwned}'),

  anythingPending: or('model.isNew', 'anythingDirty'),

  update: task(function * () {
    let model = this.get('model');
    let errors = yield this.get('data').validate(model);
    if (Object.keys(errors).length > 0) {
      return this['on-error'](errors);
    }
    yield model.save();
  }).keepLatest(),

  cancel: task(function * () {
    this.get('tools').setEditing(false);

    if (this.get('anythingDirty')) {
      let model = yield this.get('model');

      model.rollbackAttributes();
      model.rollbackRelationships();
      model.reload(); // this is to rollback attributes that are arrays

      if (model.dirtyTrackingRelationNames) {
        let relationNames = Object.keys(model.dirtyTrackingRelationNames);
        relationNames.forEach(relation => {
          let relationItems = model.get(relation);
          relationItems.forEach(item => {
            item.rollbackAttributes();
            item.rollbackRelationships();
          });
        });
      }
    }
  }),

  delete: task(function * () {
    yield this.get('model').destroyRecord();

    this.get('router').transitionTo('application');
  }),

  actions: {
    open() {
      this.set('opened', true);
    },
    close() {
      this.set('opened', false);
    }
  }
});

function animationRules() {
  this.transition(
    this.fromValue(false),
    this.toValue(true),
    this.use('to-down', { duration: 250 }),
    this.reverse('to-up', { duration: 250 })
  );
}
<|endoftext|>


<commit_before>import { sendGoogleAnalyticsEvent } from './googleAnalytics.js';
import { previewEndTime, previewStartTime } from './mediaPlayer.js';
import { convertHHMMSSToSeconds, convertSecToHHMMSS, isHHMMSS,
         isInt, isUUID } from './utility.js';

export function initClipper () {

  $('#make-clip-start-time i').on('click', function () {
    var startTime = $('#make-clip-start-time input').val() || "0";
    var endTime = $('#make-clip-end-time input').val();
    startTime = convertHHMMSSToSeconds(startTime);
    endTime = convertHHMMSSToSeconds(endTime);
    previewStartTime(startTime, endTime);
  });

  $('#make-clip-end-time i').on('click', function () {
    var endTime = $('#make-clip-end-time input').val() || "3";
    endTime = convertHHMMSSToSeconds(endTime);
    previewEndTime(endTime);
  });

  $('#make-clip-time-set').on('click', function () {
    var currentTimeHHMMSS = convertSecToHHMMSS(Math.floor(audio.currentTime));
    var currentSetMode = $('#make-clip-time-set').html();
    if (currentSetMode === 'Set Start') {
      $('#make-clip-start-time input').val(currentTimeHHMMSS);
      $('#make-clip-end-time input').focus();
      $('#make-clip-time-set').html('Set End');
      sendGoogleAnalyticsEvent('Make Clip', 'Set End button clicked')
    } else if (currentSetMode === 'Set End') {
      $('#make-clip-end-time input').val(currentTimeHHMMSS);
      $('#make-clip-time-set').html('Set Start');
      $('#make-clip-title textarea').focus();
      sendGoogleAnalyticsEvent('Make Clip', 'Set Start button clicked')
    }
  });

  $('#player').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-set').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-back').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-forwards').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-back').on('click', function () {
    audio.currentTime = audio.currentTime - 15;
  });

  $('#make-clip-time-forwards').on('click', function () {
    audio.currentTime = audio.currentTime + 15;
  });

}

export function setStartTime () {
  $('#make-clip-start-time input').val(convertSecToHHMMSS(Math.floor(audio.currentTime)));
  $('#make-clip-end-time input').focus();
}

export function setEndTime () {
  $('#make-clip-end-time input').val(convertSecToHHMMSS(Math.floor(audio.currentTime)));
  $('#make-clip-title textarea').focus();
}

export function toggleMakeClipWidget (_this) {
  if ($('#add-to-playlist').css('display') !== 'block' && $('#recommend').css('display') !== 'block') {
    $('#player-description-truncated').hide();
    $('#player-description-full').hide();
    $('#playlist').hide();
  }

  if ($(_this).hasClass('active')) {
    $('#player-description-truncated').show();
    $('#playlist').show();
  }

  $('#add-to-playlist').hide();
  $('#toggle-playlist-btn').removeClass('active');

  $('#recommend').hide();
  $('#toggle-recommend-btn').removeClass('active');

  $('#toggle-make-clip-btn').toggleClass('active');
  $('#make-clip').toggle();

  sendGoogleAnalyticsEvent('Make Clip', 'Toggle Make Clip Widget');
}

export function makeClip (event) {
  event.preventDefault();

  sendGoogleAnalyticsEvent('Make Clip', 'Make Clip');

  $('#make-clip-start-time-error').hide();
  $('#make-clip-end-time-error').hide();
  $('#make-clip-start-time').removeClass('has-danger');
  $('#make-clip-end-time').removeClass('has-danger');
  $('#make-clip-time').removeClass('has-danger');

  var startTime = $('#make-clip-start-time input').val(),
      endTime = $('#make-clip-end-time input').val();

  if (startTime === '' && endTime === '') {
    $('#make-clip-start-time-error').html('Type a start time and/or end time to create a clip.');
    $('#make-clip-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  if (!isHHMMSS(startTime) && startTime !== '') {
    $('#make-clip-start-time-error').html('Type time in hh:mm:ss format.');
    $('#make-clip-start-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  if (!isHHMMSS(endTime) && endTime !== '') {
    $('#make-clip-end-time-error').html('Type time in hh:mm:ss format.');
    $('#make-clip-end-time').addClass('has-danger');
    $('#make-clip-end-time-error').show();
    return;
  }

  startTime = convertHHMMSSToSeconds(startTime);
  endTime = convertHHMMSSToSeconds(endTime);

  if (endTime === 0) {
    endTime = null;
  }

  if (startTime === endTime) {
    $('#make-clip-start-time-error').html('Start time and end time cannot be the same.');
    $('#make-clip-start-time').addClass('has-danger');
    $('#make-clip-end-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  if (endTime !== null && endTime <= startTime) {
    $('#make-clip-start-time-error').html('End time cannot be earlier than start time.');
    $('#make-clip-start-time').addClass('has-danger');
    $('#make-clip-end-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  // TODO: how can we prevent malicious scripts in the title?
  var clipTitle = $('#make-clip-title textarea').val();

  // TODO: set owner name based on ownerId
  var ownerName = 'random owner name';

  if (endTime === "") {
    endTime = null;
  }

  // TODO: HACK: DANGER WILL ROBINSON: passing window variables into a POST
  // can't be a good idea. Should seriously consider fixing this...

  let dataObj = {
    startTime: startTime,
    endTime: endTime,
    title: clipTitle,
    ownerName: ownerName,
    podcastFeedURL: window.podcastFeedURL,
    podcastTitle: window.podcastTitle,
    episodeMediaURL: window.episodeMediaURL,
    episodeTitle: window.episodeTitle,
    episodePubDate: window.episodePubDate,
    episodeSummary: window.episodeSummary,
    episodeDuration: window.episodeDuration
  }

  if (window.podcastImageURL && window.podcastImageURL.indexOf('http') === 0) {
    dataObj.podcastImageURL = window.podcastImageURL;
  }

  if (window.episodeImageURL && window.episodeImageURL.indexOf('http') === 0) {
    dataObj.episodeImageURL = window.episodeImageURL;
  }

  $.ajax({
    type: 'POST',
    url: '/clips',
    headers: {
      Authorization: $.cookie('idToken')
    },
    data: dataObj,
    success: function (response) {
      if (window.isPlayerPage) {
        toggleMakeClipWidget();
        $('#playlist').show();
        $('#make-clip-start-time input').val('');
        $('#make-clip-end-time input').val('');
        $('#make-clip-title textarea').val('');
        $('#player-description-truncated').show();
        $('#clip-created-modal-link').val(location.protocol + '\/\/' + location.hostname + (location.port ? ':'+location.port: '')  + '\/clips\/' + response.id);
        $('#clip-created-modal').modal('show');
      } else {
        location.href = '\/clips\/' + response.id;
      }
    },
    error: function (xhr, status, error) {
      console.log(error);
      alert('Failed to create clip. Please check your internet connection and try again.');
    }

  });

}
<commit_msg>Prevent resubmit of clip maker<commit_after>import { sendGoogleAnalyticsEvent } from './googleAnalytics.js';
import { previewEndTime, previewStartTime } from './mediaPlayer.js';
import { convertHHMMSSToSeconds, convertSecToHHMMSS, isHHMMSS,
         isInt, isUUID } from './utility.js';

export function initClipper () {

  $('#make-clip-start-time i').on('click', function () {
    var startTime = $('#make-clip-start-time input').val() || "0";
    var endTime = $('#make-clip-end-time input').val();
    startTime = convertHHMMSSToSeconds(startTime);
    endTime = convertHHMMSSToSeconds(endTime);
    previewStartTime(startTime, endTime);
  });

  $('#make-clip-end-time i').on('click', function () {
    var endTime = $('#make-clip-end-time input').val() || "3";
    endTime = convertHHMMSSToSeconds(endTime);
    previewEndTime(endTime);
  });

  $('#make-clip-time-set').on('click', function () {
    var currentTimeHHMMSS = convertSecToHHMMSS(Math.floor(audio.currentTime));
    var currentSetMode = $('#make-clip-time-set').html();
    if (currentSetMode === 'Set Start') {
      $('#make-clip-start-time input').val(currentTimeHHMMSS);
      $('#make-clip-end-time input').focus();
      $('#make-clip-time-set').html('Set End');
      sendGoogleAnalyticsEvent('Make Clip', 'Set End button clicked')
    } else if (currentSetMode === 'Set End') {
      $('#make-clip-end-time input').val(currentTimeHHMMSS);
      $('#make-clip-time-set').html('Set Start');
      $('#make-clip-title textarea').focus();
      sendGoogleAnalyticsEvent('Make Clip', 'Set Start button clicked')
    }
  });

  $('#player').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-set').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-back').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-forwards').on('mousedown', function (e) {
    e.preventDefault(e);
  });

  $('#make-clip-time-back').on('click', function () {
    audio.currentTime = audio.currentTime - 15;
  });

  $('#make-clip-time-forwards').on('click', function () {
    audio.currentTime = audio.currentTime + 15;
  });

}

export function setStartTime () {
  $('#make-clip-start-time input').val(convertSecToHHMMSS(Math.floor(audio.currentTime)));
  $('#make-clip-end-time input').focus();
}

export function setEndTime () {
  $('#make-clip-end-time input').val(convertSecToHHMMSS(Math.floor(audio.currentTime)));
  $('#make-clip-title textarea').focus();
}

export function toggleMakeClipWidget (_this) {
  if ($('#add-to-playlist').css('display') !== 'block' && $('#recommend').css('display') !== 'block') {
    $('#player-description-truncated').hide();
    $('#player-description-full').hide();
    $('#playlist').hide();
  }

  if ($(_this).hasClass('active')) {
    $('#player-description-truncated').show();
    $('#playlist').show();
  }

  $('#add-to-playlist').hide();
  $('#toggle-playlist-btn').removeClass('active');

  $('#recommend').hide();
  $('#toggle-recommend-btn').removeClass('active');

  $('#toggle-make-clip-btn').toggleClass('active');
  $('#make-clip').toggle();

  sendGoogleAnalyticsEvent('Make Clip', 'Toggle Make Clip Widget');
}

export function makeClip (event) {
  event.preventDefault();

  if (window.preventResubmit) { return }

  window.preventResubmit = true;
  $('#make-clip-btn').attr('disabled', true);
  $('#make-clip-btn').html('<i class="fa fa-spinner fa-spin"></i>')

  sendGoogleAnalyticsEvent('Make Clip', 'Make Clip');

  $('#make-clip-start-time-error').hide();
  $('#make-clip-end-time-error').hide();
  $('#make-clip-start-time').removeClass('has-danger');
  $('#make-clip-end-time').removeClass('has-danger');
  $('#make-clip-time').removeClass('has-danger');

  var startTime = $('#make-clip-start-time input').val(),
      endTime = $('#make-clip-end-time input').val();

  if (startTime === '' && endTime === '') {
    $('#make-clip-start-time-error').html('Type a start time and/or end time to create a clip.');
    $('#make-clip-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  if (!isHHMMSS(startTime) && startTime !== '') {
    $('#make-clip-start-time-error').html('Type time in hh:mm:ss format.');
    $('#make-clip-start-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  if (!isHHMMSS(endTime) && endTime !== '') {
    $('#make-clip-end-time-error').html('Type time in hh:mm:ss format.');
    $('#make-clip-end-time').addClass('has-danger');
    $('#make-clip-end-time-error').show();
    return;
  }

  startTime = convertHHMMSSToSeconds(startTime);
  endTime = convertHHMMSSToSeconds(endTime);

  if (endTime === 0) {
    endTime = null;
  }

  if (startTime === endTime) {
    $('#make-clip-start-time-error').html('Start time and end time cannot be the same.');
    $('#make-clip-start-time').addClass('has-danger');
    $('#make-clip-end-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  if (endTime !== null && endTime <= startTime) {
    $('#make-clip-start-time-error').html('End time cannot be earlier than start time.');
    $('#make-clip-start-time').addClass('has-danger');
    $('#make-clip-end-time').addClass('has-danger');
    $('#make-clip-start-time-error').show();
    return;
  }

  // TODO: how can we prevent malicious scripts in the title?
  var clipTitle = $('#make-clip-title textarea').val();

  // TODO: set owner name based on ownerId
  var ownerName = 'random owner name';

  if (endTime === "") {
    endTime = null;
  }

  // TODO: HACK: DANGER WILL ROBINSON: passing window variables into a POST
  // can't be a good idea. Should seriously consider fixing this...

  let dataObj = {
    startTime: startTime,
    endTime: endTime,
    title: clipTitle,
    ownerName: ownerName,
    podcastFeedURL: window.podcastFeedURL,
    podcastTitle: window.podcastTitle,
    episodeMediaURL: window.episodeMediaURL,
    episodeTitle: window.episodeTitle,
    episodePubDate: window.episodePubDate,
    episodeSummary: window.episodeSummary,
    episodeDuration: window.episodeDuration
  }

  if (window.podcastImageURL && window.podcastImageURL.indexOf('http') === 0) {
    dataObj.podcastImageURL = window.podcastImageURL;
  }

  if (window.episodeImageURL && window.episodeImageURL.indexOf('http') === 0) {
    dataObj.episodeImageURL = window.episodeImageURL;
  }

  setTimeout(function () {
    $.ajax({
      type: 'POST',
      url: '/clips',
      headers: {
        Authorization: $.cookie('idToken')
      },
      data: dataObj,
      success: function (response) {
        if (window.isPlayerPage) {
          toggleMakeClipWidget();
          $('#playlist').show();
          $('#make-clip-start-time input').val('');
          $('#make-clip-end-time input').val('');
          $('#make-clip-title textarea').val('');
          $('#player-description-truncated').show();
          $('#clip-created-modal-link').val(location.protocol + '\/\/' + location.hostname + (location.port ? ':'+location.port: '')  + '\/clips\/' + response.id);
          $('#clip-created-modal').modal('show');
        } else {
          location.href = '\/clips\/' + response.id;
        }
      },
      error: function (xhr, status, error) {
        console.log(error);
        alert('Failed to create clip. Please check your internet connection and try again.');
      },
      done: function () {
        window.preventResubmit = false;
        $('#make-clip-btn').removeAttr('disabled');
      }
    });
  }, 3000);

}
<|endoftext|>


<commit_before>Session = Class("Session");

// XXX could use some tests

Session.constructor(function (_super) {
  var self = this;
  _super();

  self.keys = {};

  self.next_id = 1;
  self.key_callbacks = {}; // key -> id -> func
  self.key_value_callbacks = {}; // key -> value -> id -> func
});

Session.methods({
  // XXX remove debugging method (or improve it, but anyway, don't
  // ship it in production)
  dump_state: function () {
    var self = this;
    console.log("=== Session state ===");
    for (var key in self.key_callbacks) {
      var ids = _.keys(self.key_callbacks[key]);
      if (!ids.length)
        continue;
      console.log(key + ": " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
    }

    for (var key in self.key_value_callbacks) {
      for (var value in self.key_value_callbacks[key]) {
        var ids = _.keys(self.key_value_callbacks[key][value]);
        if (!ids.length)
          continue;
        console.log(key + "(" + value + "): " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
      }
    }
  },

  set: function (key, value) {
    var self = this;

    var old_value = self.keys[key];
    if (value === old_value)
      return;
    self.keys[key] = value;
    self._ensureKey(key);

    var activated = {};
    for (var id in self.key_callbacks[key])
      self.key_callbacks[key][id]();

    if (old_value in self.key_value_callbacks[key])
      for (var id in self.key_value_callbacks[key][old_value])
        self.key_value_callbacks[key][old_value][id]();

    if (value in self.key_value_callbacks[key])
      for (var id in self.key_value_callbacks[key][value])
        self.key_value_callbacks[key][value][id]();
  },

  get: function (key) {
    var self = this;

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      self.key_callbacks[key][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_callbacks[key][id];
      });
    }

    return self.keys[key];
  },

  equals: function (key, value) {
    var self = this;

    if (typeof(value) !== 'string' &&
        typeof(value) !== 'number' &&
        typeof(value) !== 'boolean' &&
        value !== null && value !== undefined)
      throw new Error("Session.equals: value can't be an object");

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      if (!(value in self.key_value_callbacks[key]))
        self.key_value_callbacks[key][value] = {};
      self.key_value_callbacks[key][value][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_value_callbacks[key][value][id];

        // clean up [key][value] if it's now empty, so we don't use
        // O(n) memory for n = values seen ever
        for (var x in self.key_value_callbacks[key][value])
          return;
        delete self.key_value_callbacks[key][value];
      });
    }

    return self.keys[key] === value;
  },

  _ensureKey: function (key) {
    var self = this;
    if (!(key in self.key_callbacks)) {
      self.key_callbacks[key] = {};
      self.key_value_callbacks[key] = {};
    }
  }
});

// singleton
Session = Session.create();
<commit_msg>Work around to prevent infinite loop in IE8.<commit_after>Session = Class("Session");

// XXX could use some tests

Session.constructor(function (_super) {
  var self = this;
  _super();

  self.keys = {};

  self.next_id = 1;
  self.key_callbacks = {}; // key -> id -> func
  self.key_value_callbacks = {}; // key -> value -> id -> func
});

Session.methods({
  // XXX remove debugging method (or improve it, but anyway, don't
  // ship it in production)
  dump_state: function () {
    var self = this;
    console.log("=== Session state ===");
    for (var key in self.key_callbacks) {
      var ids = _.keys(self.key_callbacks[key]);
      if (!ids.length)
        continue;
      console.log(key + ": " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
    }

    for (var key in self.key_value_callbacks) {
      for (var value in self.key_value_callbacks[key]) {
        var ids = _.keys(self.key_value_callbacks[key][value]);
        if (!ids.length)
          continue;
        console.log(key + "(" + value + "): " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
      }
    }
  },

  set: function (key, value) {
    var self = this;

    var old_value = self.keys[key];
    if (value === old_value)
      return;
    self.keys[key] = value;
    self._ensureKey(key);

    // Old code did this:
    //   for (var id in self.key_callbacks[key])
    //     self.key_callbacks[key][id]();
    //
    // This works on all browsers except IE8. The problem is that the
    // callback we call will (most likely) add a new callback to
    // self.key_callbacks. On IE8 this results in an infinite
    // loop. Suprisingly, this does work on other browsers, they don't
    // include newly added keys in the iteration.
    //
    // Work around this on IE8 by iterating over a copy.

    _.each(_.keys(self.key_callbacks[key]), function (id) {
      self.key_callbacks[key][id]();
    });

    if (old_value in self.key_value_callbacks[key])
      _.each(_.keys(self.key_value_callbacks[key][old_value]), function (id) {
        self.key_value_callbacks[key][old_value][id]();
      });

    if (value in self.key_value_callbacks[key])
      _.each(_.keys(self.key_value_callbacks[key][value]), function (id) {
        self.key_value_callbacks[key][value][id]();
      });
  },

  get: function (key) {
    var self = this;

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      self.key_callbacks[key][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_callbacks[key][id];
      });
    }

    return self.keys[key];
  },

  equals: function (key, value) {
    var self = this;

    if (typeof(value) !== 'string' &&
        typeof(value) !== 'number' &&
        typeof(value) !== 'boolean' &&
        value !== null && value !== undefined)
      throw new Error("Session.equals: value can't be an object");

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      if (!(value in self.key_value_callbacks[key]))
        self.key_value_callbacks[key][value] = {};
      self.key_value_callbacks[key][value][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_value_callbacks[key][value][id];

        // clean up [key][value] if it's now empty, so we don't use
        // O(n) memory for n = values seen ever
        for (var x in self.key_value_callbacks[key][value])
          return;
        delete self.key_value_callbacks[key][value];
      });
    }

    return self.keys[key] === value;
  },

  _ensureKey: function (key) {
    var self = this;
    if (!(key in self.key_callbacks)) {
      self.key_callbacks[key] = {};
      self.key_value_callbacks[key] = {};
    }
  }
});

// singleton
Session = Session.create();
<|endoftext|>


<commit_before>/*

Default list, single, and total resolvers

*/

import { Utils, debug, debugGroup, debugGroupEnd } from 'meteor/vulcan:core';
import { createError } from 'apollo-errors';

const defaultOptions = {
  cacheMaxAge: 300
}

export const getDefaultResolvers = (collectionName, resolverOptions = defaultOptions) => {

  return {

    // resolver for returning a list of documents based on a set of query terms

    list: {

      name: `${collectionName}List`,

      description: `A list of ${collectionName} documents matching a set of query terms`,

      async resolver(root, { terms = {}, enableCache = false }, context, { cacheControl }) {
        debug('')
        debugGroup(`--------------- start \x1b[35m${collectionName} list\x1b[0m resolver ---------------`);
        debug(`Options: ${JSON.stringify(resolverOptions)}`);
        debug(`Terms: ${JSON.stringify(terms)}`);

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        // get currentUser and Users collection from context
        const { currentUser, Users } = context;

        // get collection based on collectionName argument
        const collection = context[collectionName];

        // get selector and options from terms and perform Mongo query
        let { selector, options } = await collection.getParameters(terms, {}, context);
        options.skip = terms.offset;

        debug({ selector, options });

        const docs = collection.find(selector, options).fetch();

        // if collection has a checkAccess function defined, remove any documents that doesn't pass the check
        const viewableDocs = collection.checkAccess ? _.filter(docs, doc => collection.checkAccess(currentUser, doc)) : docs;

        // take the remaining documents and remove any fields that shouldn't be accessible
        const restrictedDocs = Users.restrictViewableFields(currentUser, collection, viewableDocs);

        // prime the cache
        restrictedDocs.forEach(doc => collection.loader.prime(doc._id, doc));

        debug(`\x1b[33m=> ${restrictedDocs.length} documents returned\x1b[0m`);
        debugGroupEnd();
        debug(`--------------- end \x1b[35m${collectionName} list\x1b[0m resolver ---------------`);
        debug('')

        // return results
        return restrictedDocs;
      },

    },

    // resolver for returning a single document queried based on id or slug

    single: {

      name: `${collectionName}Single`,

      description: `A single ${collectionName} document fetched by ID or slug`,

      async resolver(root, { documentId, slug, enableCache = false }, context, { cacheControl }) {
        debug('')
        debugGroup(`--------------- start \x1b[35m${collectionName} single\x1b[0m resolver ---------------`);
        debug(`Options: ${JSON.stringify(resolverOptions)}`);
        debug(`DocumentId: ${documentId}`);

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        const { currentUser, Users } = context;
        const collection = context[collectionName];

        // don't use Dataloader if doc is selected by slug
        const doc = documentId ? await collection.loader.load(documentId) : (slug ? collection.findOne({ slug }) : collection.findOne());

        if (!doc) {
          const MissingDocumentError = createError('app.missing_document', { message: 'app.missing_document' });
          throw new MissingDocumentError({ data: { documentId, slug } });
        }

        // if collection has a checkAccess function defined, use it to perform a check on the current document
        // (will throw an error if check doesn't pass)
        if (collection.checkAccess) {
          Utils.performCheck(collection.checkAccess, currentUser, doc, collection, documentId);
        }

        const restrictedDoc = Users.restrictViewableFields(currentUser, collection, doc);

        debugGroupEnd();
        debug(`--------------- end \x1b[35m${collectionName} single\x1b[0m resolver ---------------`);
        debug('')

        // filter out disallowed properties and return resulting document
        return restrictedDoc;
      },

    },

    // resolver for returning the total number of documents matching a set of query terms

    total: {

      name: `${collectionName}Total`,

      description: `The total count of ${collectionName} documents matching a set of query terms`,

      async resolver(root, { terms, enableCache }, context, { cacheControl }) {

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        const collection = context[collectionName];

        const { selector } = await collection.getParameters(terms, {}, context);

        return collection.find(selector).count();
      },

    }
  }

};
<commit_msg>Respect checkAccess for total resolver<commit_after>/*

Default list, single, and total resolvers

*/

import { Utils, debug, debugGroup, debugGroupEnd } from 'meteor/vulcan:core';
import { createError } from 'apollo-errors';

const defaultOptions = {
  cacheMaxAge: 300
}

export const getDefaultResolvers = (collectionName, resolverOptions = defaultOptions) => {

  return {

    // resolver for returning a list of documents based on a set of query terms

    list: {

      name: `${collectionName}List`,

      description: `A list of ${collectionName} documents matching a set of query terms`,

      async resolver(root, { terms = {}, enableCache = false }, context, { cacheControl }) {
        debug('')
        debugGroup(`--------------- start \x1b[35m${collectionName} list\x1b[0m resolver ---------------`);
        debug(`Options: ${JSON.stringify(resolverOptions)}`);
        debug(`Terms: ${JSON.stringify(terms)}`);

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        // get currentUser and Users collection from context
        const { currentUser, Users } = context;

        // get collection based on collectionName argument
        const collection = context[collectionName];

        // get selector and options from terms and perform Mongo query
        let { selector, options } = await collection.getParameters(terms, {}, context);
        options.skip = terms.offset;

        debug({ selector, options });

        const docs = collection.find(selector, options).fetch();

        // if collection has a checkAccess function defined, remove any documents that doesn't pass the check
        const viewableDocs = collection.checkAccess ? _.filter(docs, doc => collection.checkAccess(currentUser, doc)) : docs;

        // take the remaining documents and remove any fields that shouldn't be accessible
        const restrictedDocs = Users.restrictViewableFields(currentUser, collection, viewableDocs);

        // prime the cache
        restrictedDocs.forEach(doc => collection.loader.prime(doc._id, doc));

        debug(`\x1b[33m=> ${restrictedDocs.length} documents returned\x1b[0m`);
        debugGroupEnd();
        debug(`--------------- end \x1b[35m${collectionName} list\x1b[0m resolver ---------------`);
        debug('')

        // return results
        return restrictedDocs;
      },

    },

    // resolver for returning a single document queried based on id or slug

    single: {

      name: `${collectionName}Single`,

      description: `A single ${collectionName} document fetched by ID or slug`,

      async resolver(root, { documentId, slug, enableCache = false }, context, { cacheControl }) {
        debug('')
        debugGroup(`--------------- start \x1b[35m${collectionName} single\x1b[0m resolver ---------------`);
        debug(`Options: ${JSON.stringify(resolverOptions)}`);
        debug(`DocumentId: ${documentId}`);

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        const { currentUser, Users } = context;
        const collection = context[collectionName];

        // don't use Dataloader if doc is selected by slug
        const doc = documentId ? await collection.loader.load(documentId) : (slug ? collection.findOne({ slug }) : collection.findOne());

        if (!doc) {
          const MissingDocumentError = createError('app.missing_document', { message: 'app.missing_document' });
          throw new MissingDocumentError({ data: { documentId, slug } });
        }

        // if collection has a checkAccess function defined, use it to perform a check on the current document
        // (will throw an error if check doesn't pass)
        if (collection.checkAccess) {
          Utils.performCheck(collection.checkAccess, currentUser, doc, collection, documentId);
        }

        const restrictedDoc = Users.restrictViewableFields(currentUser, collection, doc);

        debugGroupEnd();
        debug(`--------------- end \x1b[35m${collectionName} single\x1b[0m resolver ---------------`);
        debug('')

        // filter out disallowed properties and return resulting document
        return restrictedDoc;
      },

    },

    // resolver for returning the total number of documents matching a set of query terms

    total: {

      name: `${collectionName}Total`,

      description: `The total count of ${collectionName} documents matching a set of query terms`,

      async resolver(root, { terms, enableCache }, context, { cacheControl }) {

        if (cacheControl && enableCache) {
          const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
          cacheControl.setCacheHint({ maxAge });
        }

        const collection = context[collectionName];

        const { selector } = await collection.getParameters(terms, {}, context);

        const docs = collection.find(selector);

        // if collection has a checkAccess function defined, remove any documents that doesn't pass the check
        if (collection.checkAccess) {
          const { currentUser } = context;
          return docs.fetch().filter(doc => collection.checkAccess(currentUser, doc)).length;
        }

        return docs.count();
      },
  }

};
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Robin Assistant
// @description Growth in peace
// @namespace   com.github.leoverto
// @include     https://www.reddit.com/robin/
// @include     https://www.reddit.com/robin
// @version     1.10
// @author      LeoVerto, Wiiplay123, Getnamo, K2L8M11N2
// @grant       none
// ==/UserScript==

var autoVote = true;
var disableVoteMsgs = true;
var filterSpam = true;
var filterNonAscii = true;
var version = "1.10";

var ownName = $('.user a').text();
var filteredSpamCount = 0;
var filteredVoteCount = 0;
var filteredNonAsciiCount = 0;
var userCount = 0;

var votes = {
  grow: 0,
  stay: 0,
  abandon: 0,
  abstain: 0,
  action: 'Unknown'
}

var votesLastUpdated = 0;

var startTime = new Date();

var manualThaiList = ["̍", "̎", "̄", "̅", "̿", "̑", "̆", "̐", "͒", "͗", "\
", "͑", "̇", "̈", "̊", "͂", "̓", "̈́", "͊", "͋", "͌", "\
", "̃", "̂", "̌", "͐", "̀", "́", "̋", "̏", "̒", "̓", "\
", "̔", "̽", "̉", "ͣ", "ͤ", "ͥ", "ͦ", "ͧ", "ͨ", "ͩ", "\
", "ͪ", "ͫ", "ͬ", "ͭ", "ͮ", "ͯ", "̾", "͛", "͆", "̚", "\
", "̕", "̛", "̀", "́", "͘", "̡", "̢", "̧", "̨", "̴", "\
", "̵", "̶", "", "͜", "͝", "͞", "͟", "͠", "͢", "̸", "\
", "̷", "͡", "҉", "\
", "̖", "̗", "̘", "̙", "̜", "̝", "̞", "̟", "̠", "̤", "\
", "̥", "̦", "̩", "̪", "̫", "̬", "̭", "̮", "̯", "̰", "\
", "̱", "̲", "̳", "̹", "̺", "̻", "̼", "ͅ", "͇", "͈", "\
", "͉", "͍", "͎", "͓", "͔", "͕", "͖", "͙", "͚", "̣", "\
"];

var spamBlacklist = ["spam the most used",
  "ຈل͜ຈ", "hail the", "autovoter", "staying", "﷽", "group to stay", "pasta",
  "automatically voted", "stayers are betrayers", "stayers aint players",
  "mins remaining. status", ">>>>", "TRUMPSBUTTPIRATES2016",
  "TRUMPSFIERYPOOPS2016", "ALL HAIL THE TACO BELL BOT", "#420", "้", "็", "◕_◕",
  "<<<<", "growing is all we know", "f it ends on you", "heil", "hitler",
  "timecube", "\( ͡° ͜ʖ ͡°\)", "◕", "guys can you please not spam the chat",
  "ｍｅｍｅｓ ｏｆ ｃａｐｉｔａｌｉｓｍ", "𝐁𝐄𝐑𝐍𝐈𝐄 𝐒𝐀𝐍𝐃𝐌𝐀𝐍", "█▄█▄",  "卐",
  "spam the most used phrase", "moob hunter", "someone in chat annoying",
  "cool ppl list"
];

var nonEnglishSpamRegex = "[^\x00-\x7F]+";

function rewriteCSS() {
  $(".robin-chat--body").css({
    "height": "80vh"
  });
}

function sendMessage(msg) {
  $(".text-counter-input")[0].value = msg;
  $(".text-counter-input")[0].nextSibling.click();
}

// Custom options
function addOptions() {
  // Remove possible existing custom options
  $("#customOptions").remove();

  var customOptions = document.createElement("div");
  customOptions.id = "customOptions";
  customOptions.className =
    "robin-chat--sidebar-widget robin-chat--notification-widget";

  var header = "Robin-Assistant " + version +
    " Configuration"

  var autoVoteOption = createCheckbox("auto-vote",
    "Automatically vote Grow", autoVote, autoVoteListener, false);

  var filters = "Filters"

  var filterVotesOption = createCheckbox("filter-votes",
    "Vote Messages", disableVoteMsgs, disableVoteMsgsListener, true);
  var filterSpamOption = createCheckbox("filter-spam",
    "Common spam", filterSpam, filterSpamListener, true);
  var filterNonAsciiOption = createCheckbox("filter-nonascii",
    "Non-ascii", filterNonAscii, filterNonAsciiListener, true);

  var userCounter =
    "
Users here: 0";
  var voteGrow =
    "
Grow: 0";
  var voteStay =
    "
Stay: 0";
  var voteAbandon =
    "
Abandon: 0";
  var voteAbstain =
    "
Abstain: 0";
  var timer =
    "
Time Left: 0";
  var nextAction =
    "
Unknown";

  $(customOptions).insertAfter("#robinDesktopNotifier");
  $(customOptions).append(header);
  $(customOptions).append(autoVoteOption);
  $(customOptions).append(filters);
  $(customOptions).append(filterVotesOption);
  $(customOptions).append(filterSpamOption);
  $(customOptions).append(filterNonAsciiOption);
  $(customOptions).append(userCounter);
  $(customOptions).append(voteGrow);
  $(customOptions).append(voteStay);
  $(customOptions).append(voteAbandon);
  $(customOptions).append(voteAbstain);
  $(customOptions).append(nextAction);
  $(customOptions).append(timer);
}

function createCheckbox(name, description, checked, listener, counter) {
  var label = document.createElement("label");

  var checkbox = document.createElement("input");
  checkbox.name = name;
  checkbox.type = "checkbox";
  checkbox.onclick = listener;
  $(checkbox).prop("checked", checked);

  var description = document.createTextNode(description);

  label.appendChild(checkbox);
  label.appendChild(description);

  if (counter) {
    var counter = " Filtered: 0";
    $(label).append(counter);
  }

  return label;
}

// Listeners
function disableVoteMsgsListener(event) {
  if (event !== undefined) {
    disableVoteMsgs = $(event.target).is(":checked");
  }
}

function autoVoteListener(event) {
  if (event !== undefined) {
    autoVote = $(event.target).is(":checked");
  }
}

function filterSpamListener(event) {
  if (event !== undefined) {
    filterSpam = $(event.target).is(":checked");
  }
}

function filterNonAsciiListener(event) {
  if (event !== undefined) {
    filterNonAscii = $(event.target).is(":checked");
  }
}

function addMins(date, mins) {
  var newDateObj = new Date(date.getTime() + mins * 60000);
  return newDateObj;
}

function howLongLeft() { // mostly from /u/Yantrio
  var soonMessageArray = $(".robin-message--message:contains('soon')");
  if (soonMessageArray.length > 0) {
    // for cases where it says "soon" instead of a time on page load
    return "Soon";
  }

  var remainingMessageArray = $(".robin-message--message:contains('approx')");

  if (remainingMessageArray.length == 0) {
    //This shouldn't happen
    return "Unknown";
  }

  var message = remainingMessageArray.text();
  var time = new Date(jQuery(
    ".robin--user-class--system:contains('approx') .robin-message--timestamp"
  ).attr("datetime"));
  try {
    var endTime = addMins(time, message.match(/\d+/)[0]);
    var fraction = Math.floor((endTime - new Date()) / 60 / 1000 * 10) / 10;
    var min = Math.floor(fraction);
    var sec = Math.round((fraction - min) * 60);
    return min + " m " + sec + " s";
  } catch (e) {
    return "Fail";
  }

  //grab the timestamp from the first post and then calc the difference using the estimate it gives you on boot
}

function updateCounter(id, value) {
  $("#" + id).text(value);
}

// Spam Filter
function checkSpam(message) {
  // Check for 6 or more repetitions of the same character
  if (message.search(/(.)\1{5,}/) != -1) {
    filteredSpamCount += 1;
    updateCounter("filter-spam-counter", filteredSpamCount);
    console.log("Blocked spam message (Repetition): " + message);
    return true;
  }

  if(filterNonAscii){
    if(message.match(nonEnglishSpamRegex)){
      filteredNonAsciiCount += 1;
      updateCounter("filter-nonascii-counter", filteredNonAsciiCount);
      console.log("Blocked spam message (non-ASCII): " + message);
      return true;
    }
  }

  for (o = 0; o < spamBlacklist.length; o++) {
    if (message.toLowerCase().search(spamBlacklist[o]) != -1) {
      filteredSpamCount += 1;
      updateCounter("filter-spam-counter", filteredSpamCount);
      console.log("Blocked spam message (Blacklist): " + message);
      return true;
    }
  }
  return false;
}

// Generic updates
function update() {
  updateCounter("time-left", howLongLeft());

  // update vote counters
  updateCounter("vote-grow", votes.grow);
  updateCounter("vote-stay", votes.stay);
  updateCounter("vote-abandon", votes.abandon);
  updateCounter("vote-abstain", votes.abstain);

  userCount = votes.grow + votes.stay + votes.abandon + votes.abstain;
  updateCounter("user-count", userCount);

  updateCounter("next-action", "Next round we will " + votes.action);
}

// Triggered whenever someone votes
function updateVotes() {
  // Cancel if updated during last 10 seconds
  if (Date.now() - votesLastUpdated < 10000) {
    return false;
  }

  console.log("Updating vote tally...");
  jQuery.get("/robin/", function(a) {
    var start = "{" + a.substring(a.indexOf("\"robin_user_list\": ["));
    var end = start.substring(0, start.indexOf("}]") + 2) + "}";
    list = JSON.parse(end).robin_user_list;
    votes.grow = list.filter(function(voter) {
      return voter.vote === "INCREASE"
    }).length;
    votes.stay = list.filter(function(voter) {
      return voter.vote === "CONTINUE"
    }).length;
    votes.abandon = list.filter(function(voter) {
      return voter.vote === "ABANDON"
    }).length;
    votes.abstain = list.filter(function(voter) {
      return voter.vote === "NOVOTE"
    }).length;

    var majority = userCount / 2;
    if (votes.grow > majority) {
      votes.action = "Grow";
    } else if (votes.stay > majority) {
      votes.action = "Stay";
    } else if (votes.abandon > majority) {
      votes.action = "Abandon";
    } else if (votes.abstain > majority) {
      votes.action = "Abstain";
    } else {
      vote.action = "No majority";
    }
  });

  votesLastUpdated = Date.now();
  return true;
}

// Mutation observer for new messages
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    var added = mutation.addedNodes[0];

    // Filters all new messages
    if ($(added).hasClass("robin-message")) {
      var msg = added;
      var msgText = $(msg).find(".robin-message--message").text();
      //console.log(msgText)

      // Highlight messages containing own user name
      var re = new RegExp(ownName, "i");
      if (msgText.match(re)) {
        $(msg).css({
          background: 'rgba(255, 0, 0, 0.3)',
          color: '#242424'
        });
      }

      // Filter vote messages
      if ($(msg).hasClass("robin--message-class--action") && msgText.startsWith(
          "voted to ")) {
        updateVotes();
        if (disableVoteMsgs) {
          $(msg).remove();
          console.log("Blocked spam message (Voting): " + message);
          filteredVoteCount += 1;
          updateCounter("filter-votes-counter", filteredVoteCount);
        }
        updateVotes();
      }

      // Filter spam
      if (filterSpam) {
        if (checkSpam(msgText)) {
          $(msg).remove();
        }
      }
    }
  });
});
observer.observe($("#robinChatMessageList").get(0), {
  childList: true
});

// Main run
console.log("Robin-Assistant " + version + " enabled!");

rewriteCSS();
addOptions();
updateVotes();
update();

//Check for startup messages for timing
function fetchTimeIntervals() {
  var minArray = $(".robin-message--message:contains('approx')").text().match(
    "\\d+");
}

// Auto-grow
setTimeout(function() {
  if (autoVote) {
    $(".robin--vote-class--increase")[0].click();
    console.log("Voting grow!");
  }
}, 10000);

// Update every 3 seconds
setInterval(function() {
  update();
  // Update votes at least every 30 seconds
  if (Date.now - votesLastUpdated > 30000) {
    updateVotes();
  }
}, 3000);

// Try to join robin if not in a chat once a minute
setInterval(function() {
  if ($("#joinRobinContainer".length)) {
    $("#joinRobinContainer.click()");
    setTimeout(function() {
      jQuery("#joinRobin").click();
    }, 1000);
  }
}, 60000);
<commit_msg>Blacklist contained uppercase characters, which would never be matched<commit_after>// ==UserScript==
// @name        Robin Assistant
// @description Growth in peace
// @namespace   com.github.leoverto
// @include     https://www.reddit.com/robin/
// @include     https://www.reddit.com/robin
// @version     1.10
// @author      LeoVerto, Wiiplay123, Getnamo, K2L8M11N2
// @grant       none
// ==/UserScript==

var autoVote = true;
var disableVoteMsgs = true;
var filterSpam = true;
var filterNonAscii = true;
var version = "1.10";

var ownName = $('.user a').text();
var filteredSpamCount = 0;
var filteredVoteCount = 0;
var filteredNonAsciiCount = 0;
var userCount = 0;

var votes = {
  grow: 0,
  stay: 0,
  abandon: 0,
  abstain: 0,
  action: 'Unknown'
}

var votesLastUpdated = 0;

var startTime = new Date();

var manualThaiList = ["̍", "̎", "̄", "̅", "̿", "̑", "̆", "̐", "͒", "͗", "\
", "͑", "̇", "̈", "̊", "͂", "̓", "̈́", "͊", "͋", "͌", "\
", "̃", "̂", "̌", "͐", "̀", "́", "̋", "̏", "̒", "̓", "\
", "̔", "̽", "̉", "ͣ", "ͤ", "ͥ", "ͦ", "ͧ", "ͨ", "ͩ", "\
", "ͪ", "ͫ", "ͬ", "ͭ", "ͮ", "ͯ", "̾", "͛", "͆", "̚", "\
", "̕", "̛", "̀", "́", "͘", "̡", "̢", "̧", "̨", "̴", "\
", "̵", "̶", "", "͜", "͝", "͞", "͟", "͠", "͢", "̸", "\
", "̷", "͡", "҉", "\
", "̖", "̗", "̘", "̙", "̜", "̝", "̞", "̟", "̠", "̤", "\
", "̥", "̦", "̩", "̪", "̫", "̬", "̭", "̮", "̯", "̰", "\
", "̱", "̲", "̳", "̹", "̺", "̻", "̼", "ͅ", "͇", "͈", "\
", "͉", "͍", "͎", "͓", "͔", "͕", "͖", "͙", "͚", "̣", "\
"];

var spamBlacklist = ["spam the most used",
  "ຈل͜ຈ", "hail", "autovoter", "staying", "﷽", "group to stay", "pasta",
  "automatically voted", "stayers are betrayers", "stayers aint players",
  "mins remaining. status", ">>>>", "trump", "#420", "้", "็", "◕_◕",
  "<<<<", "growing is all we know", "f it ends on you", "heil", "hitler",
  "timecube", "\( ͡° ͜ʖ ͡°\)", "◕", "guys can you please not spam the chat",
  "ｍｅｍｅｓ ｏｆ ｃａｐｉｔａｌｉｓｍ", "𝐁𝐄𝐑𝐍𝐈𝐄 𝐒𝐀𝐍𝐃𝐌𝐀𝐍", "█▄█▄",  "卐",
  "spam the most used phrase", "moob hunter", "someone in chat annoying",
  "cool ppl list"
];

var nonEnglishSpamRegex = "[^\x00-\x7F]+";

function rewriteCSS() {
  $(".robin-chat--body").css({
    "height": "80vh"
  });
}

function sendMessage(msg) {
  $(".text-counter-input")[0].value = msg;
  $(".text-counter-input")[0].nextSibling.click();
}

// Custom options
function addOptions() {
  // Remove possible existing custom options
  $("#customOptions").remove();

  var customOptions = document.createElement("div");
  customOptions.id = "customOptions";
  customOptions.className =
    "robin-chat--sidebar-widget robin-chat--notification-widget";

  var header = "Robin-Assistant " + version +
    " Configuration"

  var autoVoteOption = createCheckbox("auto-vote",
    "Automatically vote Grow", autoVote, autoVoteListener, false);

  var filters = "Filters"

  var filterVotesOption = createCheckbox("filter-votes",
    "Vote Messages", disableVoteMsgs, disableVoteMsgsListener, true);
  var filterSpamOption = createCheckbox("filter-spam",
    "Common spam", filterSpam, filterSpamListener, true);
  var filterNonAsciiOption = createCheckbox("filter-nonascii",
    "Non-ascii", filterNonAscii, filterNonAsciiListener, true);

  var userCounter =
    "
Users here: 0";
  var voteGrow =
    "
Grow: 0";
  var voteStay =
    "
Stay: 0";
  var voteAbandon =
    "
Abandon: 0";
  var voteAbstain =
    "
Abstain: 0";
  var timer =
    "
Time Left: 0";
  var nextAction =
    "
Unknown";

  $(customOptions).insertAfter("#robinDesktopNotifier");
  $(customOptions).append(header);
  $(customOptions).append(autoVoteOption);
  $(customOptions).append(filters);
  $(customOptions).append(filterVotesOption);
  $(customOptions).append(filterSpamOption);
  $(customOptions).append(filterNonAsciiOption);
  $(customOptions).append(userCounter);
  $(customOptions).append(voteGrow);
  $(customOptions).append(voteStay);
  $(customOptions).append(voteAbandon);
  $(customOptions).append(voteAbstain);
  $(customOptions).append(nextAction);
  $(customOptions).append(timer);
}

function createCheckbox(name, description, checked, listener, counter) {
  var label = document.createElement("label");

  var checkbox = document.createElement("input");
  checkbox.name = name;
  checkbox.type = "checkbox";
  checkbox.onclick = listener;
  $(checkbox).prop("checked", checked);

  var description = document.createTextNode(description);

  label.appendChild(checkbox);
  label.appendChild(description);

  if (counter) {
    var counter = " Filtered: 0";
    $(label).append(counter);
  }

  return label;
}

// Listeners
function disableVoteMsgsListener(event) {
  if (event !== undefined) {
    disableVoteMsgs = $(event.target).is(":checked");
  }
}

function autoVoteListener(event) {
  if (event !== undefined) {
    autoVote = $(event.target).is(":checked");
  }
}

function filterSpamListener(event) {
  if (event !== undefined) {
    filterSpam = $(event.target).is(":checked");
  }
}

function filterNonAsciiListener(event) {
  if (event !== undefined) {
    filterNonAscii = $(event.target).is(":checked");
  }
}

function addMins(date, mins) {
  var newDateObj = new Date(date.getTime() + mins * 60000);
  return newDateObj;
}

function howLongLeft() { // mostly from /u/Yantrio
  var soonMessageArray = $(".robin-message--message:contains('soon')");
  if (soonMessageArray.length > 0) {
    // for cases where it says "soon" instead of a time on page load
    return "Soon";
  }

  var remainingMessageArray = $(".robin-message--message:contains('approx')");

  if (remainingMessageArray.length == 0) {
    //This shouldn't happen
    return "Unknown";
  }

  var message = remainingMessageArray.text();
  var time = new Date(jQuery(
    ".robin--user-class--system:contains('approx') .robin-message--timestamp"
  ).attr("datetime"));
  try {
    var endTime = addMins(time, message.match(/\d+/)[0]);
    var fraction = Math.floor((endTime - new Date()) / 60 / 1000 * 10) / 10;
    var min = Math.floor(fraction);
    var sec = Math.round((fraction - min) * 60);
    return min + " m " + sec + " s";
  } catch (e) {
    return "Fail";
  }

  //grab the timestamp from the first post and then calc the difference using the estimate it gives you on boot
}

function updateCounter(id, value) {
  $("#" + id).text(value);
}

// Spam Filter
function checkSpam(message) {
  // Check for 6 or more repetitions of the same character
  if (message.search(/(.)\1{5,}/) != -1) {
    filteredSpamCount += 1;
    updateCounter("filter-spam-counter", filteredSpamCount);
    console.log("Blocked spam message (Repetition): " + message);
    return true;
  }

  if(filterNonAscii){
    if(message.match(nonEnglishSpamRegex)){
      filteredNonAsciiCount += 1;
      updateCounter("filter-nonascii-counter", filteredNonAsciiCount);
      console.log("Blocked spam message (non-ASCII): " + message);
      return true;
    }
  }

  for (o = 0; o < spamBlacklist.length; o++) {
    if (message.toLowerCase().search(spamBlacklist[o]) != -1) {
      filteredSpamCount += 1;
      updateCounter("filter-spam-counter", filteredSpamCount);
      console.log("Blocked spam message (Blacklist): " + message);
      return true;
    }
  }
  return false;
}

// Generic updates
function update() {
  updateCounter("time-left", howLongLeft());

  // update vote counters
  updateCounter("vote-grow", votes.grow);
  updateCounter("vote-stay", votes.stay);
  updateCounter("vote-abandon", votes.abandon);
  updateCounter("vote-abstain", votes.abstain);

  userCount = votes.grow + votes.stay + votes.abandon + votes.abstain;
  updateCounter("user-count", userCount);

  updateCounter("next-action", "Next round we will " + votes.action);
}

// Triggered whenever someone votes
function updateVotes() {
  // Cancel if updated during last 10 seconds
  if (Date.now() - votesLastUpdated < 10000) {
    return false;
  }

  console.log("Updating vote tally...");
  jQuery.get("/robin/", function(a) {
    var start = "{" + a.substring(a.indexOf("\"robin_user_list\": ["));
    var end = start.substring(0, start.indexOf("}]") + 2) + "}";
    list = JSON.parse(end).robin_user_list;
    votes.grow = list.filter(function(voter) {
      return voter.vote === "INCREASE"
    }).length;
    votes.stay = list.filter(function(voter) {
      return voter.vote === "CONTINUE"
    }).length;
    votes.abandon = list.filter(function(voter) {
      return voter.vote === "ABANDON"
    }).length;
    votes.abstain = list.filter(function(voter) {
      return voter.vote === "NOVOTE"
    }).length;

    var majority = userCount / 2;
    if (votes.grow > majority) {
      votes.action = "Grow";
    } else if (votes.stay > majority) {
      votes.action = "Stay";
    } else if (votes.abandon > majority) {
      votes.action = "Abandon";
    } else if (votes.abstain > majority) {
      votes.action = "Abstain";
    } else {
      vote.action = "No majority";
    }
  });

  votesLastUpdated = Date.now();
  return true;
}

// Mutation observer for new messages
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    var added = mutation.addedNodes[0];

    // Filters all new messages
    if ($(added).hasClass("robin-message")) {
      var msg = added;
      var msgText = $(msg).find(".robin-message--message").text();
      //console.log(msgText)

      // Highlight messages containing own user name
      var re = new RegExp(ownName, "i");
      if (msgText.match(re)) {
        $(msg).css({
          background: 'rgba(255, 0, 0, 0.3)',
          color: '#242424'
        });
      }

      // Filter vote messages
      if ($(msg).hasClass("robin--message-class--action") && msgText.startsWith(
          "voted to ")) {
        updateVotes();
        if (disableVoteMsgs) {
          $(msg).remove();
          console.log("Blocked spam message (Voting): " + message);
          filteredVoteCount += 1;
          updateCounter("filter-votes-counter", filteredVoteCount);
        }
        updateVotes();
      }

      // Filter spam
      if (filterSpam) {
        if (checkSpam(msgText)) {
          $(msg).remove();
        }
      }
    }
  });
});
observer.observe($("#robinChatMessageList").get(0), {
  childList: true
});

// Main run
console.log("Robin-Assistant " + version + " enabled!");

rewriteCSS();
addOptions();
updateVotes();
update();

//Check for startup messages for timing
function fetchTimeIntervals() {
  var minArray = $(".robin-message--message:contains('approx')").text().match(
    "\\d+");
}

// Auto-grow
setTimeout(function() {
  if (autoVote) {
    $(".robin--vote-class--increase")[0].click();
    console.log("Voting grow!");
  }
}, 10000);

// Update every 3 seconds
setInterval(function() {
  update();
  // Update votes at least every 30 seconds
  if (Date.now - votesLastUpdated > 30000) {
    updateVotes();
  }
}, 3000);

// Try to join robin if not in a chat once a minute
setInterval(function() {
  if ($("#joinRobinContainer".length)) {
    $("#joinRobinContainer.click()");
    setTimeout(function() {
      jQuery("#joinRobin").click();
    }, 1000);
  }
}, 60000);
<|endoftext|>


<commit_before>import Arr from './Array';
import Promise from './Promise';
import Str from './String';
import Super from './Super';
import { window } from './HtmlElement';
import { isFunction, assign, iterate } from './helpers';

const history = global.history;
const location = global.location;
const stoppable = new Arr(['beforeLeave', 'beforeLoad']);
const stopError = new Error();

let popstate;
let loaded;
let initialized;

export const Router = {
  $: {
    listeners: {},
    default: '/',
    icon: null,
    state: null,
    states: {},
    title: null
  },

  config(f) {
    const config = this.$;

    if (!arguments.length) {
      return config;
    }

    if (isFunction(f)) {
      f(config);
    } else {
      config.icon = f.icon || config.icon;
      config.title = f.title || config.title;
    }

    return this;
  },
  init() {
    initialized = true;

    firstLoad();

    return this;
  },
  on(event, listener) {
    if (arguments.length >= 2) {
      event = { [event]: listener };
    }

    const listeners = this.$.listeners;

    iterate(event, (listener, event) => {
      const array = listeners[event] || [];
      (listeners[event] = array).push(listener);
    });

    return this;
  },
  state(name, options) {
    const {
      controller = () => {},
      icon,
      parent,
      template = '',
      title,
      url = '/'
    } = options;

    if (this.$.states[name]) {
      throw new Error('State name must be unique');
    }

    const state = this.$.states[name] = {};

    state.controller = null;
    state.icon = icon;
    state.parent = parent;
    state.template = template;
    state.title = title;
    state.url = url;

    state.createController = function createController() {
      let ctrl;

      if (controller.prototype instanceof StateController.prototype) {
        ctrl = new controller();
      } else {
        ctrl = new StateController();
        controller.call(ctrl);
      }

      return ctrl;
    };

    return this;
  }
};

export class StateController {
  constructor() {
    Object.defineProperty(this, '$', {
      value: {
        listeners: {}
      }
    });
  }

  beforeLeave() {}
  beforeLoad() {}
  leave() {}
  load() {}
  on(event, listener) {
    if (arguments.length >= 2) {
      event = { [event]: listener };
    }

    const listeners = this.$.listeners;

    iterate(event, (listener, event) => {
      const array = listeners[event] || [];
      (listeners[event] = array).push(listener);
    });

    return this;
  }
}

window.on({
  popstate() {
    popstate = true;

    firstLoad();
  },
  DOMContentLoaded() {
    loaded = true;

    firstLoad();
  }
});

function beforeLeave(state, title, url, push) {
  const currentURL = location.pathname;
  const event = {
    from: currentURL,
    to: url,
    type: 'beforeLeave'
  };

  dispatchEvent(event).then((error) => {
    if (error !== stopError) {
      leave(state, title, url, push);
    }
  });
}
function beforeLoad(state, title, url, push) {
  const currentURL = location.pathname;
  const event = {
    from: currentURL,
    to: url,
    type: 'beforeLoad'
  };

  dispatchEvent(event).then((error) => {
    if (error !== stopError) {
      try {
        if (push) {
          pushState(state, title, url);
        } else {
          replaceState(state, title, url);
        }

        load();
      } catch (err) {
        global.location = url;
      }
    }
  });
}
function leave(state, title, url, push) {
  const currentURL = location.pathname;
  const event = {
    from: currentURL,
    to: url,
    type: 'leave'
  };

  dispatchEvent(event).then(() => {
    beforeLoad(state, title, url, push);
  });
}
function load() {
  const current = location.pathname;
  const def = Router.$.default;
  const newState = new Super(Router.$.states).find(({ urlRegExp }) => urlRegExp.test(current));

  if (newState) {
    // TODO: render template and so on
    const event = {
      url: current,
      type: 'load'
    };

    dispatchEvent(event);
  } else if (current !== def) {
    replaceState(history.state, '', def);
    load();
  } else {
    console.error(`There is no state with default url (${ def })`);
  }
}

function constructURL(url, params) {
  params = new Super(params);
  url = new Str(url);

  iterate(params, (value, param) => {
    url = url.replaceString(':' + param, value);
  });

  return url.$;
}
function dispatchEvent(event) {
  let paused;
  let stopped;
  let continuePropagation = () => {};
  let promise = Promise.resolve();

  const type = event.type;
  const currentState = Router.$.state;
  const listeners = getListeners(currentState, type);

  if (stoppable.indexOfStrict(type) === -1) {
    assign(event, {
      continue() {
        continuePropagation();
      },
      pause() {
        paused = true;
      },
      stop() {
        stopped = true;
      }
    });
  }

  listeners.forEach(({ context, listener }) => {
    promise = promise.then(() => {
      return new Promise((resolve, reject) => {
        let finished;

        continuePropagation = () => {
          if (finished) {
            resolve();
          }
        };

        listener.call(context, event);

        finished = true;

        if (stopped) {
          return reject(stopError);
        }

        if (!paused) {
          return resolve();
        }
      });
    });
  });

  return promise.catch((err) => {
    if (err !== stopError) {
      throw err;
    }

    return err;
  });
}
function getListeners(state, type) {
  const tree = [];

  while (state) {
    tree.push(state);
    state = Router.$[state.parent];
  }

  return new Arr(tree).array((array, value) => {
    const controller = value.controller;

    array.push.apply(array, new Arr(controller.$.listeners[type]).push(controller[type]).map((listener) => {
      return { context: controller, listener };
    }).$);
  }).concat(new Arr(Router.$.listeners[type]).map((listener) => {
    return { context: Router, listener };
  }));
}
function firstLoad() {
  if (popstate && loaded && initialized) {
    load();
  }
}
function pushState() {
  history.pushState.apply(history, arguments);
}
function replaceState() {
  history.replaceState.apply(history, arguments);
}

export function directByName(name, params) {
  const state = Router.$.states[name];

  if (!state) {
    throw new Error(`There is no state with given name (${ name })`);
  }

  beforeLeave(null, '', constructURL(state.url, params), true);
}
export function directTo(url) {
  beforeLeave(null, '', url, true);
}
export function redirectByName(name, params) {
  const state = Router.$.states[name];

  if (!state) {
    throw new Error(`There is no state with given name (${ name })`);
  }

  beforeLeave(null, '', constructURL(state.url, params));
}
export function redirectTo(url) {
  beforeLeave(null, '', url);
}
<commit_msg>Router: create history.<commit_after>import Arr from './Array';
import Promise from './Promise';
import Str from './String';
import Super from './Super';
import { window } from './HtmlElement';
import { isFunction, assign, iterate } from './helpers';

const history = global.history;
const location = global.location;
const stoppable = new Arr(['beforeLeave', 'beforeLoad']);
const stopError = new Error();

let loaded;
let initialized;
let routerLoaded;

export const Router = {
  $: {
    default: '/',
    icon: null,
    listeners: {},
    state: null,
    states: {},
    title: null
  },

  back() {
    return this.go(-1);
  },
  config(f) {
    const config = this.$;

    if (!arguments.length) {
      return config;
    }

    if (isFunction(f)) {
      f(config);
    } else {
      config.icon = f.icon || config.icon;
      config.title = f.title || config.title;
    }

    return this;
  },
  forward() {
    return this.go(1);
  },
  go(page) {
    history.go(page);

    return this;
  },
  history: new Arr([]),
  init() {
    initialized = true;

    firstLoad();

    return this;
  },
  on(event, listener) {
    if (arguments.length >= 2) {
      event = { [event]: listener };
    }

    const listeners = this.$.listeners;

    iterate(event, (listener, event) => {
      const array = listeners[event] || [];
      (listeners[event] = array).push(listener);
    });

    return this;
  },
  state(name, options) {
    const {
      controller = () => {},
      icon,
      parent,
      template = '',
      title,
      url = '/'
    } = options;

    if (this.$.states[name]) {
      throw new Error('State name must be unique');
    }

    const state = this.$.states[name] = {};

    state.controller = null;
    state.icon = icon;
    state.parent = parent;
    state.template = template;
    state.title = title;
    state.url = url;

    state.createController = function createController() {
      let ctrl;

      if (controller.prototype instanceof StateController.prototype) {
        ctrl = new controller();
      } else {
        ctrl = new StateController();
        controller.call(ctrl);
      }

      return ctrl;
    };

    return this;
  }
};

export class StateController {
  constructor() {
    Object.defineProperty(this, '$', {
      value: {
        listeners: {}
      }
    });
  }

  beforeLeave() {}
  beforeLoad() {}
  leave() {}
  load() {}
  on(event, listener) {
    if (arguments.length >= 2) {
      event = { [event]: listener };
    }

    const listeners = this.$.listeners;

    iterate(event, (listener, event) => {
      const array = listeners[event] || [];
      (listeners[event] = array).push(listener);
    });

    return this;
  }
}

window.on('DOMContentLoaded', () => {
  loaded = true;

  firstLoad();
});

function beforeLeave(state, title, url, push) {
  const currentURL = location.pathname;
  const event = {
    from: currentURL,
    to: url,
    type: 'beforeLeave'
  };

  dispatchEvent(event).then((error) => {
    if (error !== stopError) {
      leave(state, title, url, push);
    }
  });
}
function beforeLoad(state, title, url, push) {
  const currentURL = location.pathname;
  const event = {
    from: currentURL,
    to: url,
    type: 'beforeLoad'
  };

  dispatchEvent(event).then((error) => {
    if (error !== stopError) {
      try {
        if (push) {
          pushState(state, title, url);
        } else {
          replaceState(state, title, url);
        }

        load();
      } catch (err) {
        global.location = url;
      }
    }
  });
}
function leave(state, title, url, push) {
  const currentURL = location.pathname;
  const event = {
    from: currentURL,
    to: url,
    type: 'leave'
  };

  dispatchEvent(event).then(() => {
    beforeLoad(state, title, url, push);
  });
}
function load() {
  const current = location.pathname;
  const def = Router.$.default;
  const newState = new Super(Router.$.states).find(({ urlRegExp }) => urlRegExp.test(current));

  if (newState) {
    Router.history.push(current);
    // TODO: render template and so on
    const event = {
      url: current,
      type: 'load'
    };

    dispatchEvent(event);
  } else if (current !== def) {
    replaceState(history.state, '', def);
    load();
  } else {
    console.error(`There is no state with default url (${ def })`);
  }
}

function constructURL(url, params) {
  params = new Super(params);
  url = new Str(url);

  iterate(params, (value, param) => {
    url = url.replaceString(':' + param, value);
  });

  return url.$;
}
function dispatchEvent(event) {
  let paused;
  let stopped;
  let continuePropagation = () => {};
  let promise = Promise.resolve();

  const type = event.type;
  const currentState = Router.$.state;
  const listeners = getListeners(currentState, type);

  if (stoppable.indexOfStrict(type) === -1) {
    assign(event, {
      continue() {
        paused = false;
        continuePropagation();
      },
      pause() {
        paused = true;
      },
      stop() {
        stopped = true;
      }
    });
  }

  listeners.forEach(({ context, listener }) => {
    promise = promise.then(() => {
      return new Promise((resolve, reject) => {
        let finished;

        continuePropagation = () => {
          if (finished) {
            resolve();
          }
        };

        listener.call(context, event);

        finished = true;

        if (stopped) {
          return reject(stopError);
        }

        if (!paused) {
          return resolve();
        }
      });
    });
  });

  return promise.catch((err) => {
    if (err !== stopError) {
      throw err;
    }

    return err;
  });
}
function firstLoad() {
  if (loaded && initialized && !routerLoaded) {
    routerLoaded = true;

    window.on('popstate', () => {
      load();
    });

    beforeLoad(history.state, '', location.pathname);
  }
}
function getListeners(state, type) {
  const tree = [];

  while (state) {
    tree.push(state);
    state = Router.$[state.parent];
  }

  return new Arr(tree).array((array, value) => {
    const controller = value.controller;

    array.push.apply(array, new Arr(controller.$.listeners[type]).push(controller[type]).map((listener) => {
      return { context: controller, listener };
    }).$);
  }).concat(new Arr(Router.$.listeners[type]).map((listener) => {
    return { context: Router, listener };
  }));
}
function pushState() {
  history.pushState.apply(history, arguments);
}
function replaceState() {
  history.replaceState.apply(history, arguments);
}

export function directByName(name, params) {
  const state = Router.$.states[name];

  if (!state) {
    throw new Error(`There is no state with given name (${ name })`);
  }

  beforeLeave(null, '', constructURL(state.url, params), true);
}
export function directTo(url) {
  beforeLeave(null, '', url, true);
}
export function redirectByName(name, params) {
  const state = Router.$.states[name];

  if (!state) {
    throw new Error(`There is no state with given name (${ name })`);
  }

  beforeLeave(null, '', constructURL(state.url, params));
}
export function redirectTo(url) {
  beforeLeave(null, '', url);
}
<|endoftext|>


<commit_before>
namespace basic {

    //% block
    export function makeCode() {

    }
}
<commit_msg>Add MC color to hello world sample<commit_after>
//% color="#AA278D"
namespace basic {

    //% block
    export function makeCode() {

    }
}
<|endoftext|>