<commit_before>var EXPORTED_SYMBOLS = [];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

(function initSync() {

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://services-crypto/utils.js');
Cu.import("chrome://greasemonkey-modules/content/miscapis.js");
Cu.import('chrome://greasemonkey-modules/content/prefmanager.js');
Cu.import("chrome://greasemonkey-modules/content/storageBack.js");
Cu.import('chrome://greasemonkey-modules/content/util.js');

var gSyncInitialized = false;

try {
  // The files we're trying to import below don't exist in Firefox builds
  // without sync service, causing the import to throw.
  var gWeave = {};
  Cu.import('resource://services-sync/engines.js', gWeave);
  Cu.import('resource://services-sync/record.js', gWeave);
  Cu.import('resource://services-sync/status.js', gWeave);
  Cu.import('resource://services-sync/util.js', gWeave);
} catch (e) {
  // If there's no sync service, it doesn't make sense to continue.
  return;
}

var SyncServiceObserver = {
  init: function() {
    if (gWeave.Status.ready) {
      this.initEngine();
    } else {
      Services.obs.addObserver(this, 'weave:service:ready', true);
    }
  },

  initEngine: function() {
    if (gSyncInitialized) return;
    gSyncInitialized = true;

    // This must be delayed until after the Greasemonkey service is set up.
    Cu.import('chrome://greasemonkey-modules/content/remoteScript.js');
    // Also delay importing the actual Sync service to prevent conflicts with
    // the master password dialog during browser startup. See #1852.
    Cu.import('resource://services-sync/service.js', gWeave);

    gWeave.Service.engineManager.register(ScriptEngine);
  },

  observe: function(aSubject, aTopic, aData) {
    switch (aTopic) {
    case 'weave:service:ready':
      this.initEngine();
      break;
    }
  },

  QueryInterface: XPCOMUtils.generateQI(
      [Ci.nsIObserver, Ci.nsISupportsWeakReference]),
};

function ScriptRecord(aCollection, aId) {
  gWeave.CryptoWrapper.call(this, aCollection, aId);
}
ScriptRecord.prototype = {
  __proto__: gWeave.CryptoWrapper.prototype,
  _logName: 'Record.GreasemonkeyScript',
};
gWeave.Utils.deferGetSet(
    ScriptRecord, 'cleartext',
    ['id', 'downloadURL', 'enabled', 'installed',
     'userExcludes', 'userMatches', 'userIncludes',
     'values', 'valuesTooBig',
    ]);

function ScriptStore(aName, aEngine) {
  gWeave.Store.call(this, aName, aEngine);
}
ScriptStore.prototype = {
  __proto__: gWeave.Store.prototype,

  changeItemID: function(aOldId, aNewId) {
    dump('>>> ScriptStore.changeItemID() ... '
        + aOldId.substr(0, 8) + ' ' + aNewId.substr(0, 8) + '\n');
  },

  /// Incoming Sync record, create local version.
  create: function(aRecord) {
    if (aRecord.cleartext.installed) {
      var url = aRecord.cleartext.downloadURL;
      if (!url) {
        dump('Ignoring incoming sync record with empty downloadURL!\n');
        return;
      }
      if (!GM_util.uriFromUrl(url)) {
        dump('Ignoring incoming sync record with bad downloadURL:\n'
            + url + '\n');
        return;
      }

      var rs = new RemoteScript(aRecord.cleartext.downloadURL);
      rs.setSilent();
      rs.download(GM_util.hitch(this, function(aSuccess, aType) {
        if (aSuccess && 'dependencies' == aType) {
          rs.install();
          rs.script.enabled = aRecord.cleartext.enabled;
          rs.script.userExcludes = aRecord.cleartext.userExcludes;
          rs.script.userMatches = aRecord.cleartext.userMatches;
          rs.script.userIncludes = aRecord.cleartext.userIncludes;
          setScriptValuesFromSyncRecord(rs.script, aRecord);
        }
      }));
    } else {
      var script = scriptForSyncId(aRecord.cleartext.id);
      if (script) GM_util.getService().config.uninstall(script);
    }
  },

  /// New local item, create sync record.
  createRecord: function(aId, aCollection) {
    var script = scriptForSyncId(aId);
    var record = new ScriptRecord();
    record.cleartext.id = aId;
    if (!script) {
      // Assume this script was not found because it was uninstalled.
      record.cleartext.enabled = false;
      record.cleartext.installed = false;
    } else {
      record.cleartext.downloadURL = script.downloadURL;
      record.cleartext.enabled = script.enabled;
      record.cleartext.installed = !script.needsUninstall;
      record.cleartext.userExcludes = script.userExcludes;
      record.cleartext.userMatches = script.userMatches;
      record.cleartext.userIncludes = script.userIncludes;

      if (GM_prefRoot.getValue('sync.values')) {
        var storage = new GM_ScriptStorageBack(script);
        var totalSize = 0;
        var maxSize = GM_prefRoot.getValue('sync.values_max_size_per_script');
        record.cleartext.values = {};
        record.cleartext.valuesTooBig = false;
        var names = storage.listValues();
        for (var i = 0, name = null; name = names[i]; i++) {
          var val = storage.getValue(name);
          try {
            val = JSON.parse(val);
          } catch (e) {
            dump('JSON parse error? ' + uneval(e) + '\n');
            continue;
          }
          record.cleartext.values[name] = val;
          totalSize += name.length;
          totalSize += val.length || 4;  // 4 for number / bool (no length).

          if (totalSize > maxSize) {
            record.cleartext.values = [];
            record.cleartext.valuesTooBig = true;
            break;
          }
        }
      }
    }
    return record;
  },

  getAllIDs: function() {
    var syncIds = {};
    var scripts = GM_util.getService().config.scripts;
    for (var i = 0, script = null; script = scripts[i]; i++) {
      if (!script.downloadURL) continue;
      if (script.downloadURL.match(/^file:/)) continue;
      syncIds[syncId(script)] = 1;
    }
    return syncIds;
  },

  isAddonSyncable: function(aAddon) {
    return true;
  },

  itemExists: function(aId) {
    var script = scriptForSyncId(aId);
    return !!script;
  },

  remove: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (script) GM_util.getService().config.uninstall(this._script);
  },

  update: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (!script) {
      dump('Could not find script for record ' + aRecord.cleartext + '\n');
      return;
    }
    if (!aRecord.cleartext.installed) {
      GM_util.getService().config.uninstall(script);
    } else {
      script.enabled = !!aRecord.cleartext.enabled;
      script.userExcludes = aRecord.cleartext.userExcludes || [];
      script.userMatches = aRecord.cleartext.userMatches || [];
      script.userIncludes = aRecord.cleartext.userIncludes || [];
      setScriptValuesFromSyncRecord(script, aRecord);
    }
  },

  wipe: function() {
    dump('>>> ScriptStore.wipe() ...\n');
    // Delete everything!
  },
};

function ScriptTracker(aName, aEngine) {
  gWeave.Tracker.call(this, aName, aEngine);
  GM_util.getService().config.addObserver(this);
}
ScriptTracker.prototype = {
  __proto__: gWeave.Tracker.prototype,

  notifyEvent: function(aScript, aEvent, aData) {
    if (aEvent in {
        'install': 1, 'uninstall': 1, 'modified': 1, 'edit-enabled': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 5);
      }
    } else if (aEvent in {'cludes': 1, 'val-set': 1, 'val-del': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 1);
      }
    }
  }
};

function ScriptEngine() {
  gWeave.SyncEngine.call(this, 'Greasemonkey', gWeave.Service);

  this.enabled = GM_prefRoot.getValue('sync.enabled');
  GM_prefRoot.watch('sync.enabled', GM_util.hitch(this, function() {
    this.enabled = GM_prefRoot.getValue('sync.enabled');
  }));
}
ScriptEngine.prototype = {
  __proto__: gWeave.SyncEngine.prototype,
  _recordObj: ScriptRecord,
  _storeObj: ScriptStore,
  _trackerObj: ScriptTracker,
};

function scriptForSyncId(aSyncId) {
  var scripts = GM_util.getService().config.scripts;
  for (var i = 0, script = null; script = scripts[i]; i++) {
    if (syncId(script) == aSyncId) {
      return script;
    }
  }
}

// The sync ID for a given script.
function syncId(aScript) {
  return GM_util.sha1(aScript.id);
}

function setScriptValuesFromSyncRecord(aScript, aRecord) {
  if (GM_prefRoot.getValue('sync.values')
      && !aRecord.cleartext.valuesTooBig
  ) {
    // TODO: Clear any locally set values not in the sync record?
    var storage = new GM_ScriptStorageBack(aScript);
    for (name in aRecord.cleartext.values) {
      storage.setValue(name, aRecord.cleartext.values[name]);
    }
  }
}

SyncServiceObserver.init();
})();
<commit_msg>Typo fix reference to script object.<commit_after>var EXPORTED_SYMBOLS = [];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

(function initSync() {

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://services-crypto/utils.js');
Cu.import("chrome://greasemonkey-modules/content/miscapis.js");
Cu.import('chrome://greasemonkey-modules/content/prefmanager.js');
Cu.import("chrome://greasemonkey-modules/content/storageBack.js");
Cu.import('chrome://greasemonkey-modules/content/util.js');

var gSyncInitialized = false;

try {
  // The files we're trying to import below don't exist in Firefox builds
  // without sync service, causing the import to throw.
  var gWeave = {};
  Cu.import('resource://services-sync/engines.js', gWeave);
  Cu.import('resource://services-sync/record.js', gWeave);
  Cu.import('resource://services-sync/status.js', gWeave);
  Cu.import('resource://services-sync/util.js', gWeave);
} catch (e) {
  // If there's no sync service, it doesn't make sense to continue.
  return;
}

var SyncServiceObserver = {
  init: function() {
    if (gWeave.Status.ready) {
      this.initEngine();
    } else {
      Services.obs.addObserver(this, 'weave:service:ready', true);
    }
  },

  initEngine: function() {
    if (gSyncInitialized) return;
    gSyncInitialized = true;

    // This must be delayed until after the Greasemonkey service is set up.
    Cu.import('chrome://greasemonkey-modules/content/remoteScript.js');
    // Also delay importing the actual Sync service to prevent conflicts with
    // the master password dialog during browser startup. See #1852.
    Cu.import('resource://services-sync/service.js', gWeave);

    gWeave.Service.engineManager.register(ScriptEngine);
  },

  observe: function(aSubject, aTopic, aData) {
    switch (aTopic) {
    case 'weave:service:ready':
      this.initEngine();
      break;
    }
  },

  QueryInterface: XPCOMUtils.generateQI(
      [Ci.nsIObserver, Ci.nsISupportsWeakReference]),
};

function ScriptRecord(aCollection, aId) {
  gWeave.CryptoWrapper.call(this, aCollection, aId);
}
ScriptRecord.prototype = {
  __proto__: gWeave.CryptoWrapper.prototype,
  _logName: 'Record.GreasemonkeyScript',
};
gWeave.Utils.deferGetSet(
    ScriptRecord, 'cleartext',
    ['id', 'downloadURL', 'enabled', 'installed',
     'userExcludes', 'userMatches', 'userIncludes',
     'values', 'valuesTooBig',
    ]);

function ScriptStore(aName, aEngine) {
  gWeave.Store.call(this, aName, aEngine);
}
ScriptStore.prototype = {
  __proto__: gWeave.Store.prototype,

  changeItemID: function(aOldId, aNewId) {
    dump('>>> ScriptStore.changeItemID() ... '
        + aOldId.substr(0, 8) + ' ' + aNewId.substr(0, 8) + '\n');
  },

  /// Incoming Sync record, create local version.
  create: function(aRecord) {
    if (aRecord.cleartext.installed) {
      var url = aRecord.cleartext.downloadURL;
      if (!url) {
        dump('Ignoring incoming sync record with empty downloadURL!\n');
        return;
      }
      if (!GM_util.uriFromUrl(url)) {
        dump('Ignoring incoming sync record with bad downloadURL:\n'
            + url + '\n');
        return;
      }

      var rs = new RemoteScript(aRecord.cleartext.downloadURL);
      rs.setSilent();
      rs.download(GM_util.hitch(this, function(aSuccess, aType) {
        if (aSuccess && 'dependencies' == aType) {
          rs.install();
          rs.script.enabled = aRecord.cleartext.enabled;
          rs.script.userExcludes = aRecord.cleartext.userExcludes;
          rs.script.userMatches = aRecord.cleartext.userMatches;
          rs.script.userIncludes = aRecord.cleartext.userIncludes;
          setScriptValuesFromSyncRecord(rs.script, aRecord);
        }
      }));
    } else {
      var script = scriptForSyncId(aRecord.cleartext.id);
      if (script) GM_util.getService().config.uninstall(script);
    }
  },

  /// New local item, create sync record.
  createRecord: function(aId, aCollection) {
    var script = scriptForSyncId(aId);
    var record = new ScriptRecord();
    record.cleartext.id = aId;
    if (!script) {
      // Assume this script was not found because it was uninstalled.
      record.cleartext.enabled = false;
      record.cleartext.installed = false;
    } else {
      record.cleartext.downloadURL = script.downloadURL;
      record.cleartext.enabled = script.enabled;
      record.cleartext.installed = !script.needsUninstall;
      record.cleartext.userExcludes = script.userExcludes;
      record.cleartext.userMatches = script.userMatches;
      record.cleartext.userIncludes = script.userIncludes;

      if (GM_prefRoot.getValue('sync.values')) {
        var storage = new GM_ScriptStorageBack(script);
        var totalSize = 0;
        var maxSize = GM_prefRoot.getValue('sync.values_max_size_per_script');
        record.cleartext.values = {};
        record.cleartext.valuesTooBig = false;
        var names = storage.listValues();
        for (var i = 0, name = null; name = names[i]; i++) {
          var val = storage.getValue(name);
          try {
            val = JSON.parse(val);
          } catch (e) {
            dump('JSON parse error? ' + uneval(e) + '\n');
            continue;
          }
          record.cleartext.values[name] = val;
          totalSize += name.length;
          totalSize += val.length || 4;  // 4 for number / bool (no length).

          if (totalSize > maxSize) {
            record.cleartext.values = [];
            record.cleartext.valuesTooBig = true;
            break;
          }
        }
      }
    }
    return record;
  },

  getAllIDs: function() {
    var syncIds = {};
    var scripts = GM_util.getService().config.scripts;
    for (var i = 0, script = null; script = scripts[i]; i++) {
      if (!script.downloadURL) continue;
      if (script.downloadURL.match(/^file:/)) continue;
      syncIds[syncId(script)] = 1;
    }
    return syncIds;
  },

  isAddonSyncable: function(aAddon) {
    return true;
  },

  itemExists: function(aId) {
    var script = scriptForSyncId(aId);
    return !!script;
  },

  remove: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (script) GM_util.getService().config.uninstall(script);
  },

  update: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (!script) {
      dump('Could not find script for record ' + aRecord.cleartext + '\n');
      return;
    }
    if (!aRecord.cleartext.installed) {
      GM_util.getService().config.uninstall(script);
    } else {
      script.enabled = !!aRecord.cleartext.enabled;
      script.userExcludes = aRecord.cleartext.userExcludes || [];
      script.userMatches = aRecord.cleartext.userMatches || [];
      script.userIncludes = aRecord.cleartext.userIncludes || [];
      setScriptValuesFromSyncRecord(script, aRecord);
    }
  },

  wipe: function() {
    dump('>>> ScriptStore.wipe() ...\n');
    // Delete everything!
  },
};

function ScriptTracker(aName, aEngine) {
  gWeave.Tracker.call(this, aName, aEngine);
  GM_util.getService().config.addObserver(this);
}
ScriptTracker.prototype = {
  __proto__: gWeave.Tracker.prototype,

  notifyEvent: function(aScript, aEvent, aData) {
    if (aEvent in {
        'install': 1, 'uninstall': 1, 'modified': 1, 'edit-enabled': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 5);
      }
    } else if (aEvent in {'cludes': 1, 'val-set': 1, 'val-del': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 1);
      }
    }
  }
};

function ScriptEngine() {
  gWeave.SyncEngine.call(this, 'Greasemonkey', gWeave.Service);

  this.enabled = GM_prefRoot.getValue('sync.enabled');
  GM_prefRoot.watch('sync.enabled', GM_util.hitch(this, function() {
    this.enabled = GM_prefRoot.getValue('sync.enabled');
  }));
}
ScriptEngine.prototype = {
  __proto__: gWeave.SyncEngine.prototype,
  _recordObj: ScriptRecord,
  _storeObj: ScriptStore,
  _trackerObj: ScriptTracker,
};

function scriptForSyncId(aSyncId) {
  var scripts = GM_util.getService().config.scripts;
  for (var i = 0, script = null; script = scripts[i]; i++) {
    if (syncId(script) == aSyncId) {
      return script;
    }
  }
}

// The sync ID for a given script.
function syncId(aScript) {
  return GM_util.sha1(aScript.id);
}

function setScriptValuesFromSyncRecord(aScript, aRecord) {
  if (GM_prefRoot.getValue('sync.values')
      && !aRecord.cleartext.valuesTooBig
  ) {
    // TODO: Clear any locally set values not in the sync record?
    var storage = new GM_ScriptStorageBack(aScript);
    for (name in aRecord.cleartext.values) {
      storage.setValue(name, aRecord.cleartext.values[name]);
    }
  }
}

SyncServiceObserver.init();
})();
<|endoftext|>


<commit_before>var EXPORTED_SYMBOLS = [];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://services-crypto/utils.js');
Cu.import("resource://greasemonkey/miscapis.js");
Cu.import('resource://greasemonkey/prefmanager.js');
Cu.import('resource://greasemonkey/util.js');

var gSyncInitialized = false;

var gWeave = {};
Cu.import('resource://services-sync/engines.js', gWeave);
Cu.import('resource://services-sync/record.js', gWeave);
Cu.import('resource://services-sync/status.js', gWeave);
Cu.import('resource://services-sync/util.js', gWeave);

var SyncServiceObserver = {
  init: function() {
    if (gWeave.Status.ready) {
      this.initEngine();
    } else {
      Services.obs.addObserver(this, 'weave:service:ready', true);
    }
  },

  initEngine: function() {
    if (gSyncInitialized) return;
    gSyncInitialized = true;

    // This must be delayed until after the Greasemonkey service is set up.
    Cu.import('resource://greasemonkey/remoteScript.js');
    // Also delay importing the actual Sync service to prevent conflicts with
    // the master password dialog during browser startup. See #1852.
    Cu.import('resource://services-sync/service.js', gWeave);

    gWeave.Service.engineManager.register(ScriptEngine);
  },

  observe: function(aSubject, aTopic, aData) {
    switch (aTopic) {
    case 'weave:service:ready':
      this.initEngine();
      break;
    }
  },

  QueryInterface: XPCOMUtils.generateQI(
      [Ci.nsIObserver, Ci.nsISupportsWeakReference]),
};

function ScriptRecord(aCollection, aId) {
  gWeave.CryptoWrapper.call(this, aCollection, aId);
}
ScriptRecord.prototype = {
  __proto__: gWeave.CryptoWrapper.prototype,
  _logName: 'Record.GreasemonkeyScript',
};
gWeave.Utils.deferGetSet(
    ScriptRecord, 'cleartext',
    ['downloadURL', 'enabled', 'installed',
     'userExcludes', 'userIncludes',
     'values', 'valuesTooBig',
    ]);

function ScriptStore(aName, aEngine) {
  gWeave.Store.call(this, aName, aEngine);
}
ScriptStore.prototype = {
  __proto__: gWeave.Store.prototype,

  changeItemID: function(aOldId, aNewId) {
    dump('>>> ScriptStore.changeItemID() ... '
        + aOldId.substr(0, 8) + ' ' + aNewId.substr(0, 8) + '\n');
  },

  /// Incoming Sync record, create local version.
  create: function(aRecord) {
    if (aRecord.cleartext.installed) {
      var rs = new RemoteScript(aRecord.cleartext.downloadURL);
      rs.setSilent();
      rs.download(GM_util.hitch(this, function(aSuccess, aType) {
        if (aSuccess && 'dependencies' == aType) {
          rs.install();
          rs.script.enabled = aRecord.cleartext.enabled;
          rs.script.userExcludes = aRecord.cleartext.userExcludes;
          rs.script.userIncludes = aRecord.cleartext.userIncludes;
          setScriptValuesFromSyncRecord(rs.script, aRecord);
        }
      }));
    } else {
      var script = scriptForSyncId(aRecord.cleartext.id);
      if (!script) return;
      script.uninstall();
    }
  },

  /// New local item, create sync record.
  createRecord: function(aId, aCollection) {
    var script = scriptForSyncId(aId);
    if (script) {
      var record = new ScriptRecord();
      record.cleartext.id = aId;
      record.cleartext.downloadURL = script.downloadURL;
      record.cleartext.enabled = script.enabled;
      record.cleartext.installed = !script.needsUninstall;
      record.cleartext.userExcludes = script.userExcludes;
      record.cleartext.userIncludes = script.userIncludes;

      if (GM_prefRoot.getValue('sync.values')) {
        var storage = new GM_ScriptStorage(script);
        var totalSize = 0;
        var maxSize = GM_prefRoot.getValue('sync.values_max_size_per_script');
        record.cleartext.values = {};
        record.cleartext.valuesTooBig = false;
        var names = storage.listValues();
        for (var i = 0, name = null; name = names[i]; i++) {
          var val = storage.getValue(name);
          record.cleartext.values[name] = val;
          totalSize += name.length;
          totalSize += val.length || 4;  // 4 for number / bool (no length).

          if (totalSize > maxSize) {
            record.cleartext.values = [];
            record.cleartext.valuesTooBig = true;
            break;
          }
        }
      }

      return record;
    } else {
      // Assume this is an uninstalled script.
      var record = new ScriptRecord();
      record.cleartext.enabled = false;
      record.cleartext.installed = false;
      return record;
    }
  },

  getAllIDs: function() {
    var syncIds = {};
    var scripts = GM_util.getService().config.scripts;
    for (var i = 0, script = null; script = scripts[i]; i++) {
      if (!script.downloadURL) continue;
      if (script.downloadURL.match(/^file:/)) continue;
      syncIds[syncId(script)] = 1;
    }
    return syncIds;
  },

  itemExists: function(aId) {
    var script = scriptForSyncId(aId);
    return !!script;
  },

  remove: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (script) script.uninstall();
  },

  update: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (!script) {
      dump('Could not find script for record ' + aRecord.cleartext + '\n');
      return;
    }
    if (!aRecord.cleartext.installed) {
      script.uninstall();
    } else {
      script.enabled = aRecord.cleartext.enabled;
      script.userExcludes = aRecord.cleartext.userExcludes;
      script.userIncludes = aRecord.cleartext.userIncludes;
      setScriptValuesFromSyncRecord(script, aRecord);
    }
  },

  wipe: function() {
    dump('>>> ScriptStore.wipe() ...\n');
    // Delete everything!
  },
};

function ScriptTracker(aName, aEngine) {
  gWeave.Tracker.call(this, aName, aEngine);
  GM_util.getService().config.addObserver(this);
}
ScriptTracker.prototype = {
  __proto__: gWeave.Tracker.prototype,

  notifyEvent: function(aScript, aEvent, aData) {
    if (aEvent in {'install': 1, 'modified': 1, 'edit-enabled': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 5);
      }
    } else if (aEvent in {'cludes': 1, 'val-set': 1, 'val-del': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 1);
      }
    }
  }
};

function ScriptEngine() {
  gWeave.SyncEngine.call(this, 'Greasemonkey', gWeave.Service);

  this.enabled = GM_prefRoot.getValue('sync.enabled');
  GM_prefRoot.watch('sync.enabled', GM_util.hitch(this, function() {
    this.enabled = GM_prefRoot.getValue('sync.enabled');
  }));
}
ScriptEngine.prototype = {
  __proto__: gWeave.SyncEngine.prototype,
  _recordObj: ScriptRecord,
  _storeObj: ScriptStore,
  _trackerObj: ScriptTracker,
};

function scriptForSyncId(aSyncId) {
  var scripts = GM_util.getService().config.scripts;
  for (var i = 0, script = null; script = scripts[i]; i++) {
    if (syncId(script) == aSyncId) {
      return script;
    }
  }
}

// The sync ID for a given script.
function syncId(aScript) {
  // TODO: Salting?  e.g. btoa(CryptoUtils.generateRandomBytes(16));
  return GM_util.sha1(aScript.id);
}

function setScriptValuesFromSyncRecord(aScript, aRecord) {
  if (GM_prefRoot.getValue('sync.values')
      && !aRecord.cleartext.valuesTooBig
  ) {
    // TODO: Clear any locally set values not in the sync record?
    var storage = new GM_ScriptStorage(aScript);
    for (name in aRecord.cleartext.values) {
      storage.setValue(name, aRecord.cleartext.values[name]);
    }
  }
}

SyncServiceObserver.init()
<commit_msg>Do not install scripts from Sync without download URLs.<commit_after>var EXPORTED_SYMBOLS = [];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;

Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://services-crypto/utils.js');
Cu.import("resource://greasemonkey/miscapis.js");
Cu.import('resource://greasemonkey/prefmanager.js');
Cu.import('resource://greasemonkey/util.js');

var gSyncInitialized = false;

var gWeave = {};
Cu.import('resource://services-sync/engines.js', gWeave);
Cu.import('resource://services-sync/record.js', gWeave);
Cu.import('resource://services-sync/status.js', gWeave);
Cu.import('resource://services-sync/util.js', gWeave);

var SyncServiceObserver = {
  init: function() {
    if (gWeave.Status.ready) {
      this.initEngine();
    } else {
      Services.obs.addObserver(this, 'weave:service:ready', true);
    }
  },

  initEngine: function() {
    if (gSyncInitialized) return;
    gSyncInitialized = true;

    // This must be delayed until after the Greasemonkey service is set up.
    Cu.import('resource://greasemonkey/remoteScript.js');
    // Also delay importing the actual Sync service to prevent conflicts with
    // the master password dialog during browser startup. See #1852.
    Cu.import('resource://services-sync/service.js', gWeave);

    gWeave.Service.engineManager.register(ScriptEngine);
  },

  observe: function(aSubject, aTopic, aData) {
    switch (aTopic) {
    case 'weave:service:ready':
      this.initEngine();
      break;
    }
  },

  QueryInterface: XPCOMUtils.generateQI(
      [Ci.nsIObserver, Ci.nsISupportsWeakReference]),
};

function ScriptRecord(aCollection, aId) {
  gWeave.CryptoWrapper.call(this, aCollection, aId);
}
ScriptRecord.prototype = {
  __proto__: gWeave.CryptoWrapper.prototype,
  _logName: 'Record.GreasemonkeyScript',
};
gWeave.Utils.deferGetSet(
    ScriptRecord, 'cleartext',
    ['downloadURL', 'enabled', 'installed',
     'userExcludes', 'userIncludes',
     'values', 'valuesTooBig',
    ]);

function ScriptStore(aName, aEngine) {
  gWeave.Store.call(this, aName, aEngine);
}
ScriptStore.prototype = {
  __proto__: gWeave.Store.prototype,

  changeItemID: function(aOldId, aNewId) {
    dump('>>> ScriptStore.changeItemID() ... '
        + aOldId.substr(0, 8) + ' ' + aNewId.substr(0, 8) + '\n');
  },

  /// Incoming Sync record, create local version.
  create: function(aRecord) {
    if (aRecord.cleartext.installed) {
      var url = aRecord.cleartext.downloadURL;
      if (!url) {
        dump('Ignoring incoming sync record with empty downloadURL!\n');
        return;
      }
      if (!GM_util.uriFromUrl(url)) {
        dump('Ignoring incoming sync record with bad downloadURL:\b'
            + url + '\n');
        return;
      }

      var rs = new RemoteScript(aRecord.cleartext.downloadURL);
      rs.setSilent();
      rs.download(GM_util.hitch(this, function(aSuccess, aType) {
        if (aSuccess && 'dependencies' == aType) {
          rs.install();
          rs.script.enabled = aRecord.cleartext.enabled;
          rs.script.userExcludes = aRecord.cleartext.userExcludes;
          rs.script.userIncludes = aRecord.cleartext.userIncludes;
          setScriptValuesFromSyncRecord(rs.script, aRecord);
        }
      }));
    } else {
      var script = scriptForSyncId(aRecord.cleartext.id);
      if (!script) return;
      script.uninstall();
    }
  },

  /// New local item, create sync record.
  createRecord: function(aId, aCollection) {
    var script = scriptForSyncId(aId);
    if (script) {
      var record = new ScriptRecord();
      record.cleartext.id = aId;
      record.cleartext.downloadURL = script.downloadURL;
      record.cleartext.enabled = script.enabled;
      record.cleartext.installed = !script.needsUninstall;
      record.cleartext.userExcludes = script.userExcludes;
      record.cleartext.userIncludes = script.userIncludes;

      if (GM_prefRoot.getValue('sync.values')) {
        var storage = new GM_ScriptStorage(script);
        var totalSize = 0;
        var maxSize = GM_prefRoot.getValue('sync.values_max_size_per_script');
        record.cleartext.values = {};
        record.cleartext.valuesTooBig = false;
        var names = storage.listValues();
        for (var i = 0, name = null; name = names[i]; i++) {
          var val = storage.getValue(name);
          record.cleartext.values[name] = val;
          totalSize += name.length;
          totalSize += val.length || 4;  // 4 for number / bool (no length).

          if (totalSize > maxSize) {
            record.cleartext.values = [];
            record.cleartext.valuesTooBig = true;
            break;
          }
        }
      }

      return record;
    } else {
      // Assume this is an uninstalled script.
      var record = new ScriptRecord();
      record.cleartext.enabled = false;
      record.cleartext.installed = false;
      return record;
    }
  },

  getAllIDs: function() {
    var syncIds = {};
    var scripts = GM_util.getService().config.scripts;
    for (var i = 0, script = null; script = scripts[i]; i++) {
      if (!script.downloadURL) continue;
      if (script.downloadURL.match(/^file:/)) continue;
      syncIds[syncId(script)] = 1;
    }
    return syncIds;
  },

  itemExists: function(aId) {
    var script = scriptForSyncId(aId);
    return !!script;
  },

  remove: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (script) script.uninstall();
  },

  update: function(aRecord) {
    var script = scriptForSyncId(aRecord.cleartext.id);
    if (!script) {
      dump('Could not find script for record ' + aRecord.cleartext + '\n');
      return;
    }
    if (!aRecord.cleartext.installed) {
      script.uninstall();
    } else {
      script.enabled = aRecord.cleartext.enabled;
      script.userExcludes = aRecord.cleartext.userExcludes;
      script.userIncludes = aRecord.cleartext.userIncludes;
      setScriptValuesFromSyncRecord(script, aRecord);
    }
  },

  wipe: function() {
    dump('>>> ScriptStore.wipe() ...\n');
    // Delete everything!
  },
};

function ScriptTracker(aName, aEngine) {
  gWeave.Tracker.call(this, aName, aEngine);
  GM_util.getService().config.addObserver(this);
}
ScriptTracker.prototype = {
  __proto__: gWeave.Tracker.prototype,

  notifyEvent: function(aScript, aEvent, aData) {
    if (aEvent in {'install': 1, 'modified': 1, 'edit-enabled': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 5);
      }
    } else if (aEvent in {'cludes': 1, 'val-set': 1, 'val-del': 1}) {
      if (this.addChangedID(syncId(aScript))) {
        this.score = Math.min(100, this.score + 1);
      }
    }
  }
};

function ScriptEngine() {
  gWeave.SyncEngine.call(this, 'Greasemonkey', gWeave.Service);

  this.enabled = GM_prefRoot.getValue('sync.enabled');
  GM_prefRoot.watch('sync.enabled', GM_util.hitch(this, function() {
    this.enabled = GM_prefRoot.getValue('sync.enabled');
  }));
}
ScriptEngine.prototype = {
  __proto__: gWeave.SyncEngine.prototype,
  _recordObj: ScriptRecord,
  _storeObj: ScriptStore,
  _trackerObj: ScriptTracker,
};

function scriptForSyncId(aSyncId) {
  var scripts = GM_util.getService().config.scripts;
  for (var i = 0, script = null; script = scripts[i]; i++) {
    if (syncId(script) == aSyncId) {
      return script;
    }
  }
}

// The sync ID for a given script.
function syncId(aScript) {
  // TODO: Salting?  e.g. btoa(CryptoUtils.generateRandomBytes(16));
  return GM_util.sha1(aScript.id);
}

function setScriptValuesFromSyncRecord(aScript, aRecord) {
  if (GM_prefRoot.getValue('sync.values')
      && !aRecord.cleartext.valuesTooBig
  ) {
    // TODO: Clear any locally set values not in the sync record?
    var storage = new GM_ScriptStorage(aScript);
    for (name in aRecord.cleartext.values) {
      storage.setValue(name, aRecord.cleartext.values[name]);
    }
  }
}

SyncServiceObserver.init();
<|endoftext|>


<commit_before>import {Mongo} from 'meteor/mongo';
import {check} from 'meteor/check';

import {Games} from './games.js';
import {Pokedex} from './pokedex.js';

export const Actions = new Mongo.Collection('actions');

if (Meteor.isServer) {
    Meteor.publish('actions', function actionsPub(id) {
        return Actions.find({game: id});
    });
}

function categoryToId(c) {
    switch (c) {
        case 'cool':
            return 1;
        case 'beauty':
            return 2;
        case 'cute':
            return 3;
        case 'smart':
            return 4;
        case 'tough':
            return 5;
    }
}

function categoryCompatibility(c1, c2) {
    if (c1 == c2) return 1;
    const distance = (categoryToId(c1)+5-categoryToId(c2))%5
    if (distance == 1 || distance == 4) return 0;
    return -1;
}

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;
  while (0 !== currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }
  return array;
}

function jam(state, player, amount) {
    if (state.flags[player].block) {
        state.flags[player].block = false;
        return;
    }
    if (state.flags[player].superblock) {
        return;
    }
    if (state.flags[player].skipped) {
        return;
    }
    state.hearts[player] -= amount;
    if (state.flags[player].doublejam) state.hearts[player] -= amount;
}

Meteor.methods({
    'actions.insert'(gid, action) {
        if (!Meteor.userId()) throw new Meteor.Error('not-authorized');
        const game = Games.findOne(gid);

        Actions.insert({
            user: Meteor.userId(),
            turn: game.turn,
            game: game._id,
            createdAt: new Date(),
            action: action,
        });

        if (Actions.find({game: game._id, turn: game.turn}).count() >= game.players.length) {
            // process turn
            o = 'Turn ' + game.turn + '\n';
            pState = game.states[game.turn];
            nState = {
                excitement: pState.excitement || 0,
                flags: pState.flags || {},
                score: {},
                lastMove: {},
                hearts: {},
            };
            for (let i=0; i<pState.order.length; i++) {
                const player = pState.order[i];
                if (!nState.flags[player]) nState.flags[player] = {};

                if (nState.flags[player].stunned) {
                    nState.flags[player].skipped = true;
                    nState.flags[player].stunned = false;
                    nState.hearts[player] = 0;
                    continue;
                }
                if (nState.flags[player].dead) {
                    nState.flags[player].skipped = true;
                    nState.hearts[player] = 0;
                    continue;
                }
                if (nState.flags[player].nervous) {
                    nState.flags[player].skipped = true;
                    nState.flags[player].nervous = false;
                    nState.hearts[player] = 0;
                    continue;
                }

                const action = Actions.findOne({game: game._id, turn: game.turn, user: player});
                const move = Pokedex.moveData(action.action);
                nState.hearts[player] = move.appeal;
                const compatibility = categoryCompatibility(move.category, game.category);
                // nState.hearts[player] += compatibility;
                if (!nState.noApplause) {
                    if (move.effect_id == 13) {
                        nState.excitement++;
                    } else {
                        nState.excitement += compatibility;
                    }
                }

                if (nState.flags[player].stars) {
                    nState.hearts[player]+=nState.flags[player].stars;
                }

                if (move.effect_id != 17 && pState.lastMove && pState.lastMove[player] == move.identifier) {
                    // count how many times repeated
                    let repeats = 0;
                    for (let j=game.states.length-1;j>=0;j--) {
                        if (game.states[j].lastMove && game.states[j].lastMove[player] == move.identifier) {
                            repeats++;
                            continue;
                        }
                        break;
                    }
                    nState.hearts[player] -= 1+repeats;
                    // o += "REPEATED MOVE PENALTY OF "+(1+repeats);
                    if (!nState.noApplause && (compatibility == 1 || move.effect_id == 13)) nState.excitement -= 1; // disappointed judge
                }
                if (nState.excitement < 0) nState.excitement = 0;
                if (nState.excitement >= 5) {
                    // full excitement meter
                    nState.hearts[player] += 6;
                    nState.excitement = 0;
                }

                switch (move.effect_id) {
                    case 1:
                        // Gives a high number of appeal points with no other effects.
                        break;
                    case 2:
                        // If the Pokémon that appealed before the user earned less than three appeal points, user earns six; if three, user earns three; if more than three, user earns none.
                        if (i == 0) break;
                        const prev = nState.hearts[pState.order[i-1]];
                        if (prev < 3) nState.hearts[player] += 3;
                        if (prev > 3) nState.hearts[player] -= 3;
                        break;
                    case 3:
                        // If the user is jammed this turn after using this move, it will receive twice as many jam points.
                        nState.flags[player].doublejam = true;
                        break;
                    case 4:
                        // Attempts to jam the Pokémon that appealed before the user.
                        if (i != 0) jam(nState, pState.order[i-1], move.jam);
                        break;
                    case 5:
                        // Attempts to jam all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++)
                            jam(nState, pState.order[i-1], move.jam);
                        break;
                    case 6:
                        // Attempts to jam the other Pokémon. The user cannot make an appeal on the next turn, but it cannot be jammed either.
                        for (let j=0;j<i;j++)
                            jam(nState, pState.order[j], move.jam);
                        nState.flags[player].stunned = true;
                        break;
                    case 7:
                        // User cannot make any more appeals for the remainder of the contest.
                        nState.flags[player].dead = true;
                        break;
                    case 8:
                        // Attempts to jam all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++)
                            jam(nState, pState.order[j], move.jam);
                        break;
                    case 9:
                        // Attempts to jam the Pokémon that appealed before the user.
                        if (i != 0) jam(nState, pState.order[i-1], move.jam);
                        break;
                    case 10:
                        // Attempts to jam all Pokémon that have appealed this turn. If a Pokémon is in combo standby status, it is jammed 5 points instead of 1.
                        for (let j=0;j<i;j++) {
                            if (nState.flags[pState.order[j]].standby) {
                                jam(nState, pState.order[j], 5);
                            } else {
                                jam(nState, pState.order[j], move.jam);
                            }
                        }
                        break;
                    case 11:
                        // If the Applause meter is empty or at one, earns one point; if two, earns three points; if three, earns four points; if four, earns six points.
                        if (nState.excitement == 2) nState.hearts[player]+=2;
                        if (nState.excitement == 3) nState.hearts[player]+=3;
                        if (nState.excitement == 4) nState.hearts[player]+=5;
                        break;
                    case 12:
                        // If the last Pokémon's appeal is the same type as this move, user earns six points instead of two.
                        if (i==0) break;
                        if (nState.lastMove[pState.order[i-1]].category == move.category) {
                            nState.hearts[player]+=4;
                        }
                        break;
                    case 13:
                        // Always adds a point to the applause meter, regardless of whether the move matches the contest, and can likewise gain the applause bonus.
                        break;
                    case 14:
                        // Attempts to jam all Pokémon that have appealed this turn for half their appeal points (minimum 1).
                        for (let j=0;j<i;j++) {
                            jam(nState, pState.order[j], Math.max(1, nState.hearts[pState.order[j]]/2));
                        }
                        break;
                    case 15:
                        // Prevents jamming for the rest of this turn.
                        nState.flags[player].superblock = true;
                        break;
                    case 16:
                        // Prevents the next jam on this turn.
                        nState.flags[player].block = true;
                        break;
                    case 17:
                        // Repeated use does not incur a penalty.
                        break;
                    case 18:
                        // Attempts to make all following Pokémon nervous (and thus unable to appeal).
                        for (let j=i+1;j<pState.order.length;j++) {
                            const stars = nState.flags[pState.order[j]].stars;
                            if (!stars || stars == 0) nState.flags[pState.order[j]].nervous = true;
                        }
                        break;
                    case 19:
                        // User earns appeal points equal to the points the previous Pokémon earned plus one.
                        if (i==0) break;
                        nState.hearts[player]+=nState.hearts[pState.order[i-1]];
                        break;
                    case 20:
                        // User earns appeal points equal to half the points ALL the previous Pokémon earned plus one.
                        for (let j=0;j<i;j++) {
                            // going to assume we don't accumulate negative points
                            if (nState.hearts[pState.order[j]] > 0) {
                                nState.hearts[player]+=nState.hearts[pState.order[j]];
                            }
                        }
                        break;
                    case 21:
                        // Shuffles the next turn's turn order.
                        nState.shuffle = true;
                        break;
                    case 22:
                        // Cancels combo standby status for all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++) {
                            nState.flags[pState.order[j]].standby = false;
                        }
                        break;
                    case 23:
                        // Attempts to jam all Pokémon that have appealed this turn. If a Pokémon used the same type move as this one, it is jammed for 4 points instead of 1.
                        for (let j=0;j<i;j++) {
                            if (!nState.flags[pState.order[j]].skipped &&
                                nState.lastMove[pState.order[j]].category == move.category) {
                                jam(nState, pState.order[j], 4);
                            } else {
                                jam(nState, pState.order[j], move.jam);
                            }
                        }
                        break;
                    case 24:
                        // Prevents the Applause Meter from rising for the rest of the turn.
                        nState.noApplause = true;
                        break;
                    case 25:
                        // Randomly earns one, two, four, or eight points.
                        const choices = [0,1,3,7];
                        nState.hearts[player] += choices[Math.floor(Math.random() * choices.length)];
                        break;
                    case 26:
                        // If user appeals first this turn, earns one point; if second, two points; if third, four points; if last, six points.
                        if (i == 1) nState.hearts[player]++; // second
                        if (i == 2) nState.hearts[player]+=3; // third
                        if (i == 3) nState.hearts[player]+=5; // fourth
                        break;
                    case 27:
                        // If user appeals first this turn, earns six points instead of two.
                        if (i == 0) nState.hearts[player]+=4;
                        break;
                    case 28:
                        // If user appeals last this turn, earns six points instead of two.
                        if (i == 3) nState.hearts[player]+=4;
                        break;
                    case 29:
                        // If user has no stars, earns one point; if one, three points; if two, five points; if three, seven points. This does not include the appeal point bonus the stars give.
                        if (!nState.flags[player].stars || nState.flags[player].stars == 0) break;
                        if (nState.flags[player].stars == 1) nState.hearts[player]+=2;
                        if (nState.flags[player].stars == 2) nState.hearts[player]+=4;
                        if (nState.flags[player].stars == 3) nState.hearts[player]+=6;
                        break;
                    case 30:
                        // User will go first next turn.
                        nState.flags[player].goFirst = true;
                        break;
                    case 31:
                        // User will go last next turn.
                        nState.flags[player].goLast = true;
                        break;
                    case 32:
                        // User gains one star.
                        if (!nState.flags[player].stars) nState.flags[player].stars = 0;
                        if (nState.flags[player].stars < 3) nState.flags[player].stars++;
                        break;
                    case 33:
                        // Removes all stars from all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++) {
                            if (!nState.flags[pState.order[j]].skipped)
                                nState.flags[pState.order[j]].stars = 0;
                        }
                        break;
                }
                o += action.user + ' used ' + move.identifier + '. ';
                o += '\n';

                nState.lastMove[player] = action.action;
            }

            for (let i=0; i<pState.order.length; i++) {
                const player = pState.order[i];
                if (nState.flags[player].goFirst) nState.hearts[player]+=99;
                if (nState.flags[player].goLast) nState.hearts[player]-=99;
            }

            let order;
            if (nState.shuffle) {
                order = shuffle(pState.order);
            } else {
                order = pState.order.sort(function(a,b){return nState.hearts[a]-nState.hearts[b];});
            }

            nState.order = order;

            for (let i=0; i<pState.order.length; i++) {
                const player = pState.order[i];
                if (nState.flags[player].goFirst) nState.hearts[player]-=99;
                if (nState.flags[player].goLast) nState.hearts[player]+=99;

                nState.score[player] = pState.score[player] + nState.hearts[player]
            }

            Games.update({_id: game._id}, {
                $inc: {turn: 1},
                $push: {
                    messages: o,
                    states: nState,
                },
            });
        }
   },
});

<commit_msg>clear out the necessary flags<commit_after>import {Mongo} from 'meteor/mongo';
import {check} from 'meteor/check';

import {Games} from './games.js';
import {Pokedex} from './pokedex.js';

export const Actions = new Mongo.Collection('actions');

if (Meteor.isServer) {
    Meteor.publish('actions', function actionsPub(id) {
        return Actions.find({game: id});
    });
}

function categoryToId(c) {
    switch (c) {
        case 'cool':
            return 1;
        case 'beauty':
            return 2;
        case 'cute':
            return 3;
        case 'smart':
            return 4;
        case 'tough':
            return 5;
    }
}

function categoryCompatibility(c1, c2) {
    if (c1 == c2) return 1;
    const distance = (categoryToId(c1)+5-categoryToId(c2))%5
    if (distance == 1 || distance == 4) return 0;
    return -1;
}

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;
  while (0 !== currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }
  return array;
}

function jam(state, player, amount) {
    if (state.flags[player].block) {
        state.flags[player].block = false;
        return;
    }
    if (state.flags[player].superblock) {
        return;
    }
    if (state.flags[player].skipped) {
        return;
    }
    state.hearts[player] -= amount;
    if (state.flags[player].doublejam) state.hearts[player] -= amount;
}

Meteor.methods({
    'actions.insert'(gid, action) {
        if (!Meteor.userId()) throw new Meteor.Error('not-authorized');
        const game = Games.findOne(gid);

        Actions.insert({
            user: Meteor.userId(),
            turn: game.turn,
            game: game._id,
            createdAt: new Date(),
            action: action,
        });

        if (Actions.find({game: game._id, turn: game.turn}).count() >= game.players.length) {
            // process turn
            o = 'Turn ' + game.turn + '\n';
            pState = game.states[game.turn];
            nState = {
                excitement: pState.excitement || 0,
                flags: pState.flags || {},
                score: {},
                lastMove: {},
                hearts: {},
            };
            for (let i=0; i<pState.order.length; i++) {
                const player = pState.order[i];
                if (!nState.flags[player]) nState.flags[player] = {};

                if (nState.flags[player].stunned) {
                    nState.flags[player].skipped = true;
                    nState.flags[player].stunned = false;
                    nState.hearts[player] = 0;
                    continue;
                }
                if (nState.flags[player].dead) {
                    nState.flags[player].skipped = true;
                    nState.hearts[player] = 0;
                    continue;
                }
                if (nState.flags[player].nervous) {
                    nState.flags[player].skipped = true;
                    nState.flags[player].nervous = false;
                    nState.hearts[player] = 0;
                    continue;
                }

                const action = Actions.findOne({game: game._id, turn: game.turn, user: player});
                const move = Pokedex.moveData(action.action);
                nState.hearts[player] = move.appeal;
                const compatibility = categoryCompatibility(move.category, game.category);
                // nState.hearts[player] += compatibility;
                if (!nState.noApplause) {
                    if (move.effect_id == 13) {
                        nState.excitement++;
                    } else {
                        nState.excitement += compatibility;
                    }
                }

                if (nState.flags[player].stars) {
                    nState.hearts[player]+=nState.flags[player].stars;
                }

                if (move.effect_id != 17 && pState.lastMove && pState.lastMove[player] == move.identifier) {
                    // count how many times repeated
                    let repeats = 0;
                    for (let j=game.states.length-1;j>=0;j--) {
                        if (game.states[j].lastMove && game.states[j].lastMove[player] == move.identifier) {
                            repeats++;
                            continue;
                        }
                        break;
                    }
                    nState.hearts[player] -= 1+repeats;
                    // o += "REPEATED MOVE PENALTY OF "+(1+repeats);
                    if (!nState.noApplause && (compatibility == 1 || move.effect_id == 13)) nState.excitement -= 1; // disappointed judge
                }
                if (nState.excitement < 0) nState.excitement = 0;
                if (nState.excitement >= 5) {
                    // full excitement meter
                    nState.hearts[player] += 6;
                    nState.excitement = 0;
                }

                switch (move.effect_id) {
                    case 1:
                        // Gives a high number of appeal points with no other effects.
                        break;
                    case 2:
                        // If the Pokémon that appealed before the user earned less than three appeal points, user earns six; if three, user earns three; if more than three, user earns none.
                        if (i == 0) break;
                        const prev = nState.hearts[pState.order[i-1]];
                        if (prev < 3) nState.hearts[player] += 3;
                        if (prev > 3) nState.hearts[player] -= 3;
                        break;
                    case 3:
                        // If the user is jammed this turn after using this move, it will receive twice as many jam points.
                        nState.flags[player].doublejam = true;
                        break;
                    case 4:
                        // Attempts to jam the Pokémon that appealed before the user.
                        if (i != 0) jam(nState, pState.order[i-1], move.jam);
                        break;
                    case 5:
                        // Attempts to jam all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++)
                            jam(nState, pState.order[i-1], move.jam);
                        break;
                    case 6:
                        // Attempts to jam the other Pokémon. The user cannot make an appeal on the next turn, but it cannot be jammed either.
                        for (let j=0;j<i;j++)
                            jam(nState, pState.order[j], move.jam);
                        nState.flags[player].stunned = true;
                        break;
                    case 7:
                        // User cannot make any more appeals for the remainder of the contest.
                        nState.flags[player].dead = true;
                        break;
                    case 8:
                        // Attempts to jam all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++)
                            jam(nState, pState.order[j], move.jam);
                        break;
                    case 9:
                        // Attempts to jam the Pokémon that appealed before the user.
                        if (i != 0) jam(nState, pState.order[i-1], move.jam);
                        break;
                    case 10:
                        // Attempts to jam all Pokémon that have appealed this turn. If a Pokémon is in combo standby status, it is jammed 5 points instead of 1.
                        for (let j=0;j<i;j++) {
                            if (nState.flags[pState.order[j]].standby) {
                                jam(nState, pState.order[j], 5);
                            } else {
                                jam(nState, pState.order[j], move.jam);
                            }
                        }
                        break;
                    case 11:
                        // If the Applause meter is empty or at one, earns one point; if two, earns three points; if three, earns four points; if four, earns six points.
                        if (nState.excitement == 2) nState.hearts[player]+=2;
                        if (nState.excitement == 3) nState.hearts[player]+=3;
                        if (nState.excitement == 4) nState.hearts[player]+=5;
                        break;
                    case 12:
                        // If the last Pokémon's appeal is the same type as this move, user earns six points instead of two.
                        if (i==0) break;
                        if (nState.lastMove[pState.order[i-1]].category == move.category) {
                            nState.hearts[player]+=4;
                        }
                        break;
                    case 13:
                        // Always adds a point to the applause meter, regardless of whether the move matches the contest, and can likewise gain the applause bonus.
                        break;
                    case 14:
                        // Attempts to jam all Pokémon that have appealed this turn for half their appeal points (minimum 1).
                        for (let j=0;j<i;j++) {
                            jam(nState, pState.order[j], Math.max(1, nState.hearts[pState.order[j]]/2));
                        }
                        break;
                    case 15:
                        // Prevents jamming for the rest of this turn.
                        nState.flags[player].superblock = true;
                        break;
                    case 16:
                        // Prevents the next jam on this turn.
                        nState.flags[player].block = true;
                        break;
                    case 17:
                        // Repeated use does not incur a penalty.
                        break;
                    case 18:
                        // Attempts to make all following Pokémon nervous (and thus unable to appeal).
                        for (let j=i+1;j<pState.order.length;j++) {
                            const stars = nState.flags[pState.order[j]].stars;
                            if (!stars || stars == 0) nState.flags[pState.order[j]].nervous = true;
                        }
                        break;
                    case 19:
                        // User earns appeal points equal to the points the previous Pokémon earned plus one.
                        if (i==0) break;
                        nState.hearts[player]+=nState.hearts[pState.order[i-1]];
                        break;
                    case 20:
                        // User earns appeal points equal to half the points ALL the previous Pokémon earned plus one.
                        for (let j=0;j<i;j++) {
                            // going to assume we don't accumulate negative points
                            if (nState.hearts[pState.order[j]] > 0) {
                                nState.hearts[player]+=nState.hearts[pState.order[j]];
                            }
                        }
                        break;
                    case 21:
                        // Shuffles the next turn's turn order.
                        nState.shuffle = true;
                        break;
                    case 22:
                        // Cancels combo standby status for all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++) {
                            nState.flags[pState.order[j]].standby = false;
                        }
                        break;
                    case 23:
                        // Attempts to jam all Pokémon that have appealed this turn. If a Pokémon used the same type move as this one, it is jammed for 4 points instead of 1.
                        for (let j=0;j<i;j++) {
                            if (!nState.flags[pState.order[j]].skipped &&
                                nState.lastMove[pState.order[j]].category == move.category) {
                                jam(nState, pState.order[j], 4);
                            } else {
                                jam(nState, pState.order[j], move.jam);
                            }
                        }
                        break;
                    case 24:
                        // Prevents the Applause Meter from rising for the rest of the turn.
                        nState.noApplause = true;
                        break;
                    case 25:
                        // Randomly earns one, two, four, or eight points.
                        const choices = [0,1,3,7];
                        nState.hearts[player] += choices[Math.floor(Math.random() * choices.length)];
                        break;
                    case 26:
                        // If user appeals first this turn, earns one point; if second, two points; if third, four points; if last, six points.
                        if (i == 1) nState.hearts[player]++; // second
                        if (i == 2) nState.hearts[player]+=3; // third
                        if (i == 3) nState.hearts[player]+=5; // fourth
                        break;
                    case 27:
                        // If user appeals first this turn, earns six points instead of two.
                        if (i == 0) nState.hearts[player]+=4;
                        break;
                    case 28:
                        // If user appeals last this turn, earns six points instead of two.
                        if (i == 3) nState.hearts[player]+=4;
                        break;
                    case 29:
                        // If user has no stars, earns one point; if one, three points; if two, five points; if three, seven points. This does not include the appeal point bonus the stars give.
                        if (!nState.flags[player].stars || nState.flags[player].stars == 0) break;
                        if (nState.flags[player].stars == 1) nState.hearts[player]+=2;
                        if (nState.flags[player].stars == 2) nState.hearts[player]+=4;
                        if (nState.flags[player].stars == 3) nState.hearts[player]+=6;
                        break;
                    case 30:
                        // User will go first next turn.
                        nState.flags[player].goFirst = true;
                        break;
                    case 31:
                        // User will go last next turn.
                        nState.flags[player].goLast = true;
                        break;
                    case 32:
                        // User gains one star.
                        if (!nState.flags[player].stars) nState.flags[player].stars = 0;
                        if (nState.flags[player].stars < 3) nState.flags[player].stars++;
                        break;
                    case 33:
                        // Removes all stars from all Pokémon that have appealed this turn.
                        for (let j=0;j<i;j++) {
                            if (!nState.flags[pState.order[j]].skipped)
                                nState.flags[pState.order[j]].stars = 0;
                        }
                        break;
                }
                o += player + ' used ' + move.identifier + '. ';
                o += player + ' got ' + nState.hearts[player] + ' hearts.';
                o += '\n';

                nState.lastMove[player] = action.action;
            }

            for (let i=0; i<pState.order.length; i++) {
                const player = pState.order[i];
                if (nState.flags[player].goFirst) nState.hearts[player]+=99;
                if (nState.flags[player].goLast) nState.hearts[player]-=99;
            }

            let order;
            if (nState.shuffle) {
                order = shuffle(pState.order);
            } else {
                order = pState.order.sort(function(a,b){return nState.hearts[a]-nState.hearts[b];});
            }

            nState.order = order;

            for (let i=0; i<pState.order.length; i++) {
                const player = pState.order[i];
                if (nState.flags[player].goFirst) nState.hearts[player]-=99;
                if (nState.flags[player].goLast) nState.hearts[player]+=99;

                nState.score[player] = pState.score[player] + nState.hearts[player]

                const f = nState.flags[player];
                if (f.doublejam) f.doublejam = false;
                if (f.superblock) f.superblock = false;
                if (f.block) f.block = false;
                if (f.goFirst) f.goFirst = false;
                if (f.goLast) f.goLast = false;
                if (f.skipped) f.skipped = false;
            }

            Games.update({_id: game._id}, {
                $inc: {turn: 1},
                $push: {
                    messages: o,
                    states: nState,
                },
            });
        }
   },
});

<|endoftext|>


<commit_before>/**
 * An easy to use plugin for an offcanvas container.
 *
 * @author Lars Graubner <mail@larsgraubner.de>
 * @version 3.1.0
 * @license MIT
 */
;(function(window, document, $, undefined) {
    "use strict";

    var pluginName = "offcanvas";

    /**
     * Helper function to delay function calls.
     * http://davidwalsh.name/javascript-debounce-function
     */
    function debounce(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    }

    /**
     * Plugin constructor.
     *
     * @param {Object} element  element to use
     * @param {Object} options  options to use
     */
    function Plugin(element, options) {
        this.$el = $(element);
        var data = this.$el.data();

        for (var p in data) {
            if (data.hasOwnProperty(p) && /^offcanvas[A-Z]+/.test(p)) {
                var shortName = p[pluginName.length].toLowerCase() + p.substr(pluginName.length + 1);
                data[shortName] = data[p];
            }
        }

        this.settings = $.extend(true, {}, $.fn[pluginName].defaults, options, data);
        this._name = pluginName;
        this.$el.data(`${this._name}.opts`, this.settings);

        this.init();
    }

    /**
     * Extend prototype with functions.
     */
    $.extend(Plugin.prototype, {
        /**
         * Set height of the container.
         */
        _setHeights: function() {
            if (!this.$el.data(`${this._name}.opts`))
                return; // already initialized

            var height = this.$doc.height();
            this.$element.css("height", height);
            if (this.settings.overlay) {
                this.$overlay.css("height", height);
            }
        },

        /**
         * Function to show container.
         */
        show: function() {
            if (this._visible) {
                return; // already shown
            }

            console.log("[%s] --show--", this._name);
            this.$el.trigger(`show.${this._name}`);

            this.$cont.addClass(this.settings.classes.open);
            this._visible = true;

            this._setHeights();

            this.effects[this.settings.effect].$el.velocity({
                    [this.settings.origin]: this.effects[this.settings.effect].to
                }, $.extend({
                    complete: () => {
                        this.$el.trigger(`shown.${this._name}`);
                        if (this.settings.overlay) {
                            this.$overlay.one(`click.${this._name}`, (e) => {
                                this.hide();
                            });
                        }
                    }
                }, this.animationOptions));

            if (this.settings.overlay) {
                this.$overlay.velocity({
                    opacity: 1
                }, $.extend({
                    display: "block"
                }, this.animationOptions));
            }
        },

        /**
         * Function to hide container.
         */
        hide: function() {
            if (!this._visible) {
                return; // already hidden
            }

            console.log("[%s] --hide--", this._name);
            this.$el.trigger(`hide.${this._name}`);

            this.$cont.removeClass(this.settings.classes.open);
            this._visible = false;

            this.effects[this.settings.effect].$el.velocity("stop")
                .velocity("reverse", $.extend({
                    complete: () => {
                        this.$el.trigger(`hidden.${this._name}`);
                    }
                }, this.animationOptions));

            if (this.settings.overlay) {
                this.$overlay.velocity("stop").velocity("reverse", $.extend({
                    display: "none"
                }, this.animationOptions));
            }
        },

        /**
         * Shorthand function to toggle container.
         */
        toggle: function() {
            return (this._visible ? this.hide : this.show).call(this);
        },

        /**
         * Destroy function to remove all changes and data.
         */
        destroy: function() {
            if (!this.$el.data(`${this._name}.opts`))
                return; // no instance found

            console.log("[%s] --destroy--", this._name);
            this.$innerWrapper.unwrap();
            this.$innerWrapper.children().unwrap();

            this.$cont.removeClass(this.settings.classes.container)
                .removeClass(this.settings.classes.open);

            if (this.settings.overlay) {
                this.$overlay.off(`click.${this._name}`).remove();
            }

            this.$el.unwrap()
                .removeData(`${this._name}.opts`)
                .removeAttr("style");

            this.$win.off(`resize.${this._name}`);
        },

        init: function() {
            console.log("[%s] --init--", this._name);

            this.$win = $(window);
            this.$doc = $(document);
            this._visible = false;

            this.$cont = $(this.settings.container);
            this.$cont.addClass(this.settings.classes.container)
                .children(":not(script)")
                .wrapAll($("<div/>")
                .addClass(this.settings.classes.outer));

            this.$outerWrapper = $(`.${this.settings.classes.outer}`)
                .wrapInner($("<div/>")
                .addClass(this.settings.classes.inner));
            this.$innerWrapper = $(`.${this.settings.classes.inner}`);

            if (this.settings.overlay) {
                this.$innerWrapper.append(
                    $("<div/>")
                        .addClass(this.settings.classes.overlay)
                        .css("background-color", this.settings.overlayColor)
                );
                this.$overlay = $(`.${this.settings.classes.overlay}`);
            }

            this.$el.wrap($("<div/>")
                .addClass(this.settings.classes.element));

            this.$element = this.$el.parent();

            this.$element.css({
                width: this.settings.coverage,
                [this.settings.origin]: `-${this.settings.coverage}`
            });

            this.effects = {
                push: {
                    $el: this.$innerWrapper,
                    from: 0,
                    to: this.settings.coverage
                },
                "slide-in-over": {
                    $el: this.$element,
                    from: `-${this.settings.coverage}`,
                    to: 0
                }
            };

            this.animationOptions = {
                easing: this.settings.easing,
                duration: this.settings.duration
            };

            this.$win.on(`resize.${this._name}`, () => debounce(this._setHeights, 300));
            this._setHeights();

            this.$el.trigger(`init.${this._name}`);
        }
    });

    /**
     * Extend jQuery object with the new plugin.
     */
    $.fn[pluginName] = function(options) {
        var args = arguments;

        if (options === undefined || typeof options === "object") {
            return this.each(function() {
                if (!$.data(this, `plugin_${pluginName}`)) {
                    $.data(this, `plugin_${pluginName}`, new Plugin(this, options));
                }
            });
        } else if (typeof options === "string" && options[0] !== "_" && options !== "init") {
            var returns;

            this.each(function() {
                var instance = $.data(this, `plugin_${pluginName}`);

                if (instance instanceof Plugin && typeof instance[options] === "function") {
                    returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
                }

                if (options === "destroy") {
                    $.data(this, `plugin_${pluginName}`, null);
                }
            });

            return returns !== undefined ? returns : this;
        }

    };

    /**
     * Set plugin defaults.
     *
     * @type {Object}
     */
    $.fn[pluginName].defaults = {
        classes: {
            container: pluginName,
            element: `${pluginName}-element`,
            inner: `${pluginName}-inner`,
            open: `${pluginName}-open`,
            outer: `${pluginName}-outer`,
            overlay: `${pluginName}-overlay`
        },
        container: "body",
        coverage: "220px",
        origin: "left",
        duration: 300,
        easing: "ease-in-out",
        effect: "push",
        overlay: false,
        overlayColor: "rgba(0, 0, 0, 0.7)"
    };
})(window, document, jQuery);
<commit_msg>updated version number<commit_after>/**
 * An easy to use plugin for an offcanvas container.
 *
 * @author Lars Graubner <mail@larsgraubner.de>
 * @version 3.1.2
 * @license MIT
 */
;(function(window, document, $, undefined) {
    "use strict";

    var pluginName = "offcanvas";

    /**
     * Helper function to delay function calls.
     * http://davidwalsh.name/javascript-debounce-function
     */
    function debounce(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    }

    /**
     * Plugin constructor.
     *
     * @param {Object} element  element to use
     * @param {Object} options  options to use
     */
    function Plugin(element, options) {
        this.$el = $(element);
        var data = this.$el.data();

        for (var p in data) {
            if (data.hasOwnProperty(p) && /^offcanvas[A-Z]+/.test(p)) {
                var shortName = p[pluginName.length].toLowerCase() + p.substr(pluginName.length + 1);
                data[shortName] = data[p];
            }
        }

        this.settings = $.extend(true, {}, $.fn[pluginName].defaults, options, data);
        this._name = pluginName;
        this.$el.data(`${this._name}.opts`, this.settings);

        this.init();
    }

    /**
     * Extend prototype with functions.
     */
    $.extend(Plugin.prototype, {
        /**
         * Set height of the container.
         */
        _setHeights: function() {
            if (!this.$el.data(`${this._name}.opts`))
                return; // already initialized

            var height = this.$doc.height();
            this.$element.css("height", height);
            if (this.settings.overlay) {
                this.$overlay.css("height", height);
            }
        },

        /**
         * Function to show container.
         */
        show: function() {
            if (this._visible) {
                return; // already shown
            }

            console.log("[%s] --show--", this._name);
            this.$el.trigger(`show.${this._name}`);

            this.$cont.addClass(this.settings.classes.open);
            this._visible = true;

            this._setHeights();

            this.effects[this.settings.effect].$el.velocity({
                    [this.settings.origin]: this.effects[this.settings.effect].to
                }, $.extend({
                    complete: () => {
                        this.$el.trigger(`shown.${this._name}`);
                        if (this.settings.overlay) {
                            this.$overlay.one(`click.${this._name}`, (e) => {
                                this.hide();
                            });
                        }
                    }
                }, this.animationOptions));

            if (this.settings.overlay) {
                this.$overlay.velocity({
                    opacity: 1
                }, $.extend({
                    display: "block"
                }, this.animationOptions));
            }
        },

        /**
         * Function to hide container.
         */
        hide: function() {
            if (!this._visible) {
                return; // already hidden
            }

            console.log("[%s] --hide--", this._name);
            this.$el.trigger(`hide.${this._name}`);

            this.$cont.removeClass(this.settings.classes.open);
            this._visible = false;

            this.effects[this.settings.effect].$el.velocity("stop")
                .velocity("reverse", $.extend({
                    complete: () => {
                        this.$el.trigger(`hidden.${this._name}`);
                    }
                }, this.animationOptions));

            if (this.settings.overlay) {
                this.$overlay.velocity("stop").velocity("reverse", $.extend({
                    display: "none"
                }, this.animationOptions));
            }
        },

        /**
         * Shorthand function to toggle container.
         */
        toggle: function() {
            return (this._visible ? this.hide : this.show).call(this);
        },

        /**
         * Destroy function to remove all changes and data.
         */
        destroy: function() {
            if (!this.$el.data(`${this._name}.opts`))
                return; // no instance found

            console.log("[%s] --destroy--", this._name);
            this.$innerWrapper.unwrap();
            this.$innerWrapper.children().unwrap();

            this.$cont.removeClass(this.settings.classes.container)
                .removeClass(this.settings.classes.open);

            if (this.settings.overlay) {
                this.$overlay.off(`click.${this._name}`).remove();
            }

            this.$el.unwrap()
                .removeData(`${this._name}.opts`)
                .removeAttr("style");

            this.$win.off(`resize.${this._name}`);
        },

        init: function() {
            console.log("[%s] --init--", this._name);

            this.$win = $(window);
            this.$doc = $(document);
            this._visible = false;

            this.$cont = $(this.settings.container);
            this.$cont.addClass(this.settings.classes.container)
                .children(":not(script)")
                .wrapAll($("<div/>")
                .addClass(this.settings.classes.outer));

            this.$outerWrapper = $(`.${this.settings.classes.outer}`)
                .wrapInner($("<div/>")
                .addClass(this.settings.classes.inner));
            this.$innerWrapper = $(`.${this.settings.classes.inner}`);

            if (this.settings.overlay) {
                this.$innerWrapper.append(
                    $("<div/>")
                        .addClass(this.settings.classes.overlay)
                        .css("background-color", this.settings.overlayColor)
                );
                this.$overlay = $(`.${this.settings.classes.overlay}`);
            }

            this.$el.wrap($("<div/>")
                .addClass(this.settings.classes.element));

            this.$element = this.$el.parent();

            this.$element.css({
                width: this.settings.coverage,
                [this.settings.origin]: `-${this.settings.coverage}`
            });

            this.effects = {
                push: {
                    $el: this.$innerWrapper,
                    from: 0,
                    to: this.settings.coverage
                },
                "slide-in-over": {
                    $el: this.$element,
                    from: `-${this.settings.coverage}`,
                    to: 0
                }
            };

            this.animationOptions = {
                easing: this.settings.easing,
                duration: this.settings.duration
            };

            this.$win.on(`resize.${this._name}`, () => debounce(this._setHeights, 300));
            this._setHeights();

            this.$el.trigger(`init.${this._name}`);
        }
    });

    /**
     * Extend jQuery object with the new plugin.
     */
    $.fn[pluginName] = function(options) {
        var args = arguments;

        if (options === undefined || typeof options === "object") {
            return this.each(function() {
                if (!$.data(this, `plugin_${pluginName}`)) {
                    $.data(this, `plugin_${pluginName}`, new Plugin(this, options));
                }
            });
        } else if (typeof options === "string" && options[0] !== "_" && options !== "init") {
            var returns;

            this.each(function() {
                var instance = $.data(this, `plugin_${pluginName}`);

                if (instance instanceof Plugin && typeof instance[options] === "function") {
                    returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
                }

                if (options === "destroy") {
                    $.data(this, `plugin_${pluginName}`, null);
                }
            });

            return returns !== undefined ? returns : this;
        }

    };

    /**
     * Set plugin defaults.
     *
     * @type {Object}
     */
    $.fn[pluginName].defaults = {
        classes: {
            container: pluginName,
            element: `${pluginName}-element`,
            inner: `${pluginName}-inner`,
            open: `${pluginName}-open`,
            outer: `${pluginName}-outer`,
            overlay: `${pluginName}-overlay`
        },
        container: "body",
        coverage: "220px",
        origin: "left",
        duration: 300,
        easing: "ease-in-out",
        effect: "push",
        overlay: false,
        overlayColor: "rgba(0, 0, 0, 0.7)"
    };
})(window, document, jQuery);
<|endoftext|>