<commit_before>var chai = require('chai')
  , expect = require('chai').expect
  , sinon = require('sinon')
  , state = require('../../../lib/middleware/state')
  , SessionStore = require('../../../lib/stores/session')

  // TODO: Move this to login/federated
describe('integration: sso/oauth2', function() {

  describe('GET /login/federated', function() {

    // TODO: This test needs reviewing
    describe('with state intended for this resource', function() {
      var store = new SessionStore({ genh: function() { return 'XXXXXXXX' } })
        , request, response, err;

      before(function() {
        sinon.spy(store, 'load');
        sinon.spy(store, 'save');
        sinon.spy(store, 'update');
        sinon.spy(store, 'destroy');
      });

      before(function(done) {
        function handler(req, res, next) {
          req.state.complete();

          res.pushState({
            provider: 'https://server.example.net'
          }, 'https://server.example.com/cb', false);
          res.redirect('https://server.example.net/authorize?response_type=code&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fserver.example.com%2Fcb');
        }

        chai.express.use([state({ store: store }), handler])
          .request(function(req) {
            req.header = function(name) {
              var lc = name.toLowerCase();
              return this.headers[lc];
            }

            // TODO: this shouldn't load the state, since it is not intended for the
            //        /login/federated resource, but rather /continue.   Need to handle
            //       this on push state, to set the resume state to the parent state.
            request = req;
            request.connection = { encrypted: true };
            request.method = 'GET';
            request.url = '/login/federated?provider=https%3A%2F%2Fserver.example.net&state=00000000';
            request.headers = {
              'host': 'server.example.com',
              'referer': 'https://server.example.com/login?state=00000000'
            }
            request.query = { provider: 'https://server.example.net', state: '00000000' };
            request.session = {};
            request.session.state = {};
            request.session.state['00000000'] = {
              location: 'https://server.example.com/login/federated',
              provider: 'https://myshopify.com',
              shop: 'example.myshopify.com',
              returnTo: '/'
            };
          })
          .finish(function() {
            response = this;
            done();
          })
          .listen();
      });

      it('should correctly invoke state store', function() {
        expect(store.load).to.have.callCount(1);
        expect(store.save).to.have.callCount(1);
        expect(store.update).to.have.callCount(0);
        expect(store.destroy).to.have.callCount(0);
      });

      it('should set state', function() {
        expect(request.state).to.be.an('object');
        expect(request.state).to.deep.equal({
          location: 'https://server.example.com/cb',
          provider: 'https://server.example.net',
          returnTo: '/'
        });
      });

      it('should persist state in session', function() {
        expect(request.session).to.deep.equal({
          state: {
            // FIXME: This is the complted state, and should be removed from session
            '00000000': {
              location: 'https://server.example.com/login/federated',
              provider: 'https://myshopify.com',
              shop: 'example.myshopify.com',
              returnTo: '/'
            },
            'XXXXXXXX': {
              location: 'https://server.example.com/cb',
              provider: 'https://server.example.net',
              returnTo: '/'
            }
          }
        });
      });

      it('should redirect', function() {
        expect(response.statusCode).to.equal(302);
        expect(response.getHeader('Location')).to.equal('https://server.example.net/authorize?response_type=code&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fserver.example.com%2Fcb&state=XXXXXXXX');
      });
    }); // with state intended for this resource

  });

});

describe('GET /oauth2/redirect', function() {

  it('should consume state from state query parameter and return to location', function(done) {
    var store = new SessionStore();
    sinon.spy(store, 'load');
    sinon.spy(store, 'save');
    sinon.spy(store, 'update');
    sinon.spy(store, 'destroy');

    function handler(req, res, next) {
      expect(req.state).to.deep.equal({
        location: 'https://client.example.com/cb',
        provider: 'http://server.example.com',
        returnTo: 'https://client.example.com/'
      });
      res.resumeState(next);
    }

    function redirect(req, res, next) {
      res.redirect('/home')
    }

    chai.express.use([ state({ store: store }), handler, redirect ])
      .request(function(req, res) {
        req.method = 'GET';
        req.url = '/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj';
        req.headers = {
          'host': 'client.example.com'
        }
        req.connection = { encrypted: true };
        req.query = { code: 'SplxlOBeZQQYbYS6WxSbIA', state: 'af0ifjsldkj' };
        req.session = {};
        req.session.state = {};
        req.session.state['af0ifjsldkj'] = {
          location: 'https://client.example.com/cb',
          provider: 'http://server.example.com',
          returnTo: 'https://client.example.com/'
        };
      })
      .finish(function() {
        expect(store.load).to.have.callCount(1);
        expect(store.save).to.have.callCount(0);
        expect(store.update).to.have.callCount(0);
        expect(store.destroy).to.have.callCount(1);

        expect(this.req.session).to.deep.equal({});

        expect(this.statusCode).to.equal(302);
        expect(this.getHeader('Location')).to.equal('https://client.example.com/');
        done();
      })
      .listen();
  }); // should consume state from state query parameter and return to location

  it('should consume state from state query parameter and resume state', function(done) {
    var store = new SessionStore();
    sinon.spy(store, 'load');
    sinon.spy(store, 'save');
    sinon.spy(store, 'update');
    sinon.spy(store, 'destroy');

    function handler(req, res, next) {
      expect(req.state).to.deep.equal({
        location: 'https://server.example.com/cb',
        provider: 'https://server.example.net',
        resumeState: '00000000'
      });
      res.resumeState(next);
    }

    function redirect(req, res, next) {
      res.redirect('/home')
    }

    chai.express.use([ state({ store: store }), handler, redirect ])
      .request(function(req, res) {
        req.method = 'GET';
        req.url = '/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz';
        req.headers = {
          'host': 'server.example.com'
        }
        req.connection = { encrypted: true };
        req.query = { code: 'SplxlOBeZQQYbYS6WxSbIA', state: 'xyz' };
        req.session = {};
        req.session.state = {};
        req.session.state['xyz'] = {
          location: 'https://server.example.com/cb',
          provider: 'https://server.example.net',
          resumeState: '00000000'
        };
        req.session.state['00000000'] = {
          location: 'https://server.example.com/authorize/continue',
          clientID: 's6BhdRkqt3',
          redirectURI: 'https://client.example.com/cb',
          state: 'xyz'
        };
      })
      .finish(function() {
        expect(store.load).to.have.callCount(2);
        expect(store.save).to.have.callCount(0);
        expect(store.update).to.have.callCount(0);
        expect(store.destroy).to.have.callCount(1);

        expect(this.req.session).to.deep.equal({
          state: {
            '00000000': {
              location: 'https://server.example.com/authorize/continue',
              clientID: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              state: 'xyz'
            }
          }
        });

        expect(this.statusCode).to.equal(302);
        expect(this.getHeader('Location')).to.equal('https://server.example.com/authorize/continue?state=00000000');
        done();
      })
      .listen();
  }); // should consume state from state query parameter and resume state

  // TODO: Review this test
  it('and resuming state yeilding parameters', function(done) {
    var store = new SessionStore();
    sinon.spy(store, 'load');
    sinon.spy(store, 'save');
    sinon.spy(store, 'update');
    sinon.spy(store, 'destroy');

    // TODO: test case with multiple handlers
    function handler(req, res, next) {
      req.federatedUser = { id: '248289761001', provider: 'http://server.example.net' };
      res.resumeState({ beep: 'boop' }, next);
    }

    function redirect(req, res, next) {
      res.redirect('/home')
    }

    chai.express.use([ state({ store: store }), handler, redirect ])
      .request(function(req, res) {
        req.connection = { encrypted: true };
        req.method = 'GET';
        req.url = '/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj';
        req.headers = {
          'host': 'server.example.com'
        }
        req.query = { code: 'SplxlOBeZQQYbYS6WxSbIA', state: 'af0ifjsldkj' };
        req.session = {};
        req.session.state = {};
        req.session.state['af0ifjsldkj'] = {
          location: 'https://server.example.com/cb',
          provider: 'http://server.example.net',
          resumeState: 'Dxh5N7w_wMQ'
        };
        req.session.state['Dxh5N7w_wMQ'] = {
          location: 'https://server.example.com/oauth2/authorize/continue',
          clientID: 's6BhdRkqt3',
          redirectURI: 'https://client.example.com/cb',
          state: 'xyz'
        };
      })
      .finish(function() {
        expect(store.load).to.have.callCount(2);
        expect(store.save).to.have.callCount(0);
        expect(store.update).to.have.callCount(1);
        expect(store.destroy).to.have.callCount(1);

        expect(this.req.state).to.be.an('object');
        expect(this.req.state).to.deep.equal({
          location: 'https://server.example.com/cb',
          provider: 'http://server.example.net',
          resumeState: 'Dxh5N7w_wMQ'
        });

        expect(this.req.session).to.deep.equal({
          state: {
            'Dxh5N7w_wMQ': {
              location: 'https://server.example.com/oauth2/authorize/continue',
              clientID: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              state: 'xyz',
              beep: 'boop'
            }
          }
        });

        expect(this.statusCode).to.equal(302);
        expect(this.getHeader('Location')).to.equal('https://server.example.com/oauth2/authorize/continue?state=Dxh5N7w_wMQ');

        done();
      })
      .listen();
  }); // and resuming state yeilding parameters

  // TODO: Test case without returnTo

  // TODO: Test case for popping return data into state and/or query params
  //       (for example, when a session is not established, but the return to page needs info)

}); // redirect back from OAuth 2.0 authorization server
<commit_msg>Switch tests to req.pushState.<commit_after>var chai = require('chai')
  , expect = require('chai').expect
  , sinon = require('sinon')
  , state = require('../../../lib/middleware/state')
  , SessionStore = require('../../../lib/stores/session')

  // TODO: Move this to login/federated
describe('integration: sso/oauth2', function() {

  describe('GET /login/federated', function() {

    // TODO: This test needs reviewing
    describe('with state intended for this resource', function() {
      var store = new SessionStore({ genh: function() { return 'XXXXXXXX' } })
        , request, response, err;

      before(function() {
        sinon.spy(store, 'load');
        sinon.spy(store, 'save');
        sinon.spy(store, 'update');
        sinon.spy(store, 'destroy');
      });

      before(function(done) {
        function handler(req, res, next) {
          req.state.complete();

          req.pushState({
            provider: 'https://server.example.net'
          }, 'https://server.example.com/cb');
          res.redirect('https://server.example.net/authorize?response_type=code&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fserver.example.com%2Fcb');
        }

        chai.express.use([state({ store: store }), handler])
          .request(function(req) {
            req.header = function(name) {
              var lc = name.toLowerCase();
              return this.headers[lc];
            }

            // TODO: this shouldn't load the state, since it is not intended for the
            //        /login/federated resource, but rather /continue.   Need to handle
            //       this on push state, to set the resume state to the parent state.
            request = req;
            request.connection = { encrypted: true };
            request.method = 'GET';
            request.url = '/login/federated?provider=https%3A%2F%2Fserver.example.net&state=00000000';
            request.headers = {
              'host': 'server.example.com',
              'referer': 'https://server.example.com/login?state=00000000'
            }
            request.query = { provider: 'https://server.example.net', state: '00000000' };
            request.session = {};
            request.session.state = {};
            request.session.state['00000000'] = {
              location: 'https://server.example.com/login/federated',
              provider: 'https://myshopify.com',
              shop: 'example.myshopify.com',
              returnTo: '/'
            };
          })
          .finish(function() {
            response = this;
            done();
          })
          .listen();
      });

      it('should correctly invoke state store', function() {
        expect(store.load).to.have.callCount(1);
        expect(store.save).to.have.callCount(1);
        expect(store.update).to.have.callCount(0);
        expect(store.destroy).to.have.callCount(0);
      });

      it('should set state', function() {
        expect(request.state).to.be.an('object');
        expect(request.state).to.deep.equal({
          location: 'https://server.example.com/cb',
          provider: 'https://server.example.net',
          returnTo: '/'
        });
      });

      it('should persist state in session', function() {
        expect(request.session).to.deep.equal({
          state: {
            // FIXME: This is the complted state, and should be removed from session
            '00000000': {
              location: 'https://server.example.com/login/federated',
              provider: 'https://myshopify.com',
              shop: 'example.myshopify.com',
              returnTo: '/'
            },
            'XXXXXXXX': {
              location: 'https://server.example.com/cb',
              provider: 'https://server.example.net',
              returnTo: '/'
            }
          }
        });
      });

      it('should redirect', function() {
        expect(response.statusCode).to.equal(302);
        expect(response.getHeader('Location')).to.equal('https://server.example.net/authorize?response_type=code&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fserver.example.com%2Fcb&state=XXXXXXXX');
      });
    }); // with state intended for this resource

  });

});

describe('GET /oauth2/redirect', function() {

  it('should consume state from state query parameter and return to location', function(done) {
    var store = new SessionStore();
    sinon.spy(store, 'load');
    sinon.spy(store, 'save');
    sinon.spy(store, 'update');
    sinon.spy(store, 'destroy');

    function handler(req, res, next) {
      expect(req.state).to.deep.equal({
        location: 'https://client.example.com/cb',
        provider: 'http://server.example.com',
        returnTo: 'https://client.example.com/'
      });
      res.resumeState(next);
    }

    function redirect(req, res, next) {
      res.redirect('/home')
    }

    chai.express.use([ state({ store: store }), handler, redirect ])
      .request(function(req, res) {
        req.method = 'GET';
        req.url = '/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj';
        req.headers = {
          'host': 'client.example.com'
        }
        req.connection = { encrypted: true };
        req.query = { code: 'SplxlOBeZQQYbYS6WxSbIA', state: 'af0ifjsldkj' };
        req.session = {};
        req.session.state = {};
        req.session.state['af0ifjsldkj'] = {
          location: 'https://client.example.com/cb',
          provider: 'http://server.example.com',
          returnTo: 'https://client.example.com/'
        };
      })
      .finish(function() {
        expect(store.load).to.have.callCount(1);
        expect(store.save).to.have.callCount(0);
        expect(store.update).to.have.callCount(0);
        expect(store.destroy).to.have.callCount(1);

        expect(this.req.session).to.deep.equal({});

        expect(this.statusCode).to.equal(302);
        expect(this.getHeader('Location')).to.equal('https://client.example.com/');
        done();
      })
      .listen();
  }); // should consume state from state query parameter and return to location

  it('should consume state from state query parameter and resume state', function(done) {
    var store = new SessionStore();
    sinon.spy(store, 'load');
    sinon.spy(store, 'save');
    sinon.spy(store, 'update');
    sinon.spy(store, 'destroy');

    function handler(req, res, next) {
      expect(req.state).to.deep.equal({
        location: 'https://server.example.com/cb',
        provider: 'https://server.example.net',
        resumeState: '00000000'
      });
      res.resumeState(next);
    }

    function redirect(req, res, next) {
      res.redirect('/home')
    }

    chai.express.use([ state({ store: store }), handler, redirect ])
      .request(function(req, res) {
        req.method = 'GET';
        req.url = '/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz';
        req.headers = {
          'host': 'server.example.com'
        }
        req.connection = { encrypted: true };
        req.query = { code: 'SplxlOBeZQQYbYS6WxSbIA', state: 'xyz' };
        req.session = {};
        req.session.state = {};
        req.session.state['xyz'] = {
          location: 'https://server.example.com/cb',
          provider: 'https://server.example.net',
          resumeState: '00000000'
        };
        req.session.state['00000000'] = {
          location: 'https://server.example.com/authorize/continue',
          clientID: 's6BhdRkqt3',
          redirectURI: 'https://client.example.com/cb',
          state: 'xyz'
        };
      })
      .finish(function() {
        expect(store.load).to.have.callCount(2);
        expect(store.save).to.have.callCount(0);
        expect(store.update).to.have.callCount(0);
        expect(store.destroy).to.have.callCount(1);

        expect(this.req.session).to.deep.equal({
          state: {
            '00000000': {
              location: 'https://server.example.com/authorize/continue',
              clientID: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              state: 'xyz'
            }
          }
        });

        expect(this.statusCode).to.equal(302);
        expect(this.getHeader('Location')).to.equal('https://server.example.com/authorize/continue?state=00000000');
        done();
      })
      .listen();
  }); // should consume state from state query parameter and resume state

  // TODO: Review this test
  it('and resuming state yeilding parameters', function(done) {
    var store = new SessionStore();
    sinon.spy(store, 'load');
    sinon.spy(store, 'save');
    sinon.spy(store, 'update');
    sinon.spy(store, 'destroy');

    // TODO: test case with multiple handlers
    function handler(req, res, next) {
      req.federatedUser = { id: '248289761001', provider: 'http://server.example.net' };
      res.resumeState({ beep: 'boop' }, next);
    }

    function redirect(req, res, next) {
      res.redirect('/home')
    }

    chai.express.use([ state({ store: store }), handler, redirect ])
      .request(function(req, res) {
        req.connection = { encrypted: true };
        req.method = 'GET';
        req.url = '/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj';
        req.headers = {
          'host': 'server.example.com'
        }
        req.query = { code: 'SplxlOBeZQQYbYS6WxSbIA', state: 'af0ifjsldkj' };
        req.session = {};
        req.session.state = {};
        req.session.state['af0ifjsldkj'] = {
          location: 'https://server.example.com/cb',
          provider: 'http://server.example.net',
          resumeState: 'Dxh5N7w_wMQ'
        };
        req.session.state['Dxh5N7w_wMQ'] = {
          location: 'https://server.example.com/oauth2/authorize/continue',
          clientID: 's6BhdRkqt3',
          redirectURI: 'https://client.example.com/cb',
          state: 'xyz'
        };
      })
      .finish(function() {
        expect(store.load).to.have.callCount(2);
        expect(store.save).to.have.callCount(0);
        expect(store.update).to.have.callCount(1);
        expect(store.destroy).to.have.callCount(1);

        expect(this.req.state).to.be.an('object');
        expect(this.req.state).to.deep.equal({
          location: 'https://server.example.com/cb',
          provider: 'http://server.example.net',
          resumeState: 'Dxh5N7w_wMQ'
        });

        expect(this.req.session).to.deep.equal({
          state: {
            'Dxh5N7w_wMQ': {
              location: 'https://server.example.com/oauth2/authorize/continue',
              clientID: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              state: 'xyz',
              beep: 'boop'
            }
          }
        });

        expect(this.statusCode).to.equal(302);
        expect(this.getHeader('Location')).to.equal('https://server.example.com/oauth2/authorize/continue?state=Dxh5N7w_wMQ');

        done();
      })
      .listen();
  }); // and resuming state yeilding parameters

  // TODO: Test case without returnTo

  // TODO: Test case for popping return data into state and/or query params
  //       (for example, when a session is not established, but the return to page needs info)

}); // redirect back from OAuth 2.0 authorization server
<|endoftext|>


<commit_before>XDate.locales['fr'] = {
	monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
	monthNamesShort: ["Janv","Févr","Mars","Avr","Mai","Juin","Juil","Août","Sept","Oct","Nov","Déc"],
	dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
	dayNamesShort: ['Dim.','Lun.','Mar.','Mer.','Jeu.','Ven.','Sam.'],
	dateFormat: "dddd, d MMMM yyyy",
	dateFormatMonth: "MMMM yyyy",
	dateFormatYear: "yyyy",
	ok: "Accepter",
	cancel: "Annuler"
};
XDate.locales['de'] = {
	monthNames: ["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],
	monthNamesShort: ["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],
	dayNames: ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],
	dayNamesShort: ["So","Mo","Di","Mi","Do","Fr","Sa"],
	dateFormat: "dddd, d.MMMM yyyy",
	dateFormatMonth: "MMMM yyyy",
	dateFormatYear: "yyyy",
	ok: "\u00dcbernehmen",
	cancel: "Abbrechen"
};
XDate.locales['en'] = {
	monthNames: ["January","February","March","April","May","June","July","August","September","October","November","December"],
	monthNamesShort: ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
	dayNames: ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
	dayNamesShort: ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],
	dateFormat: "dddd, MMMM d, yyyy",
	dateFormatMonth: "MMMM yyyy",
	dateFormatYear: "yyyy",
	ok: "Accept",
	cancel: "Cancel"
};
XDate.locales['it'] = {
	monthNames: ["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],
	monthNamesShort: ["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],
	dayNames: ["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],
	dayNamesShort: ["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],
	dateFormat: "dddd, d MMMM yyyy",
	dateFormatMonth: "MMMM yyyy",
	dateFormatYear: "yyyy",
	ok: "Assumi",
	cancel: "Annulla"
};
XDate.locales['es'] = {
	monthNames: ["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],
	monthNamesShort: ["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],
	dayNames: ["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],
	dayNamesShort: ["Dom","Lun","Mar","Mié","Jue","Vie","Sáb"],
	dateFormat: "dddd, d 'de' MMMM 'de' yyyy",
	dateFormatMonth: "MMMM 'de' yyyy",
	dateFormatYear: "yyyy",
	ok: "Aplicar",
	cancel: "Cancelar"
};
XDate.locales['nl'] = {
	monthNames: ["Januari","Februari","Maart","April","Mei","Juni","Juli","Augustus","September","Oktober","November","December"],
	monthNamesShort: ["Jan","Feb","Mrt","Apr","Mei","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],
	dayNames: ["Zondag","Maandag","Dinsdag","Woensdag","Donderdag","Vrijdag","Zaterdag"],
	dayNamesShort: ["Zo","Ma","Di","Wo","Do","Vr","Za"],
	dateFormat: "dddd, d MMMM yyyy",
	dateFormatMonth: "MMMM yyyy",
	dateFormatYear: "yyyy",
	ok: "Overnemen",
	cancel: "Annuleren"
};
XDate.locales['sv'] = {
	monthNames: ["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],
	monthNamesShort: ["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],
	dayNames: ["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],
	dayNamesShort: ["Sö","Må","Ti","On","To","Fr","Lö"],
	dateFormat: "dddd, d MMMM yyyy",
	dateFormatMonth: "MMMM yyyy",
	dateFormatYear: "yyyy",
	ok: "Klar",
	cancel: "Avbryt"
};
<commit_msg>added hebrew translation<commit_after>XDate.locales['fr'] = {
  monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
  monthNamesShort: ["Janv","Févr","Mars","Avr","Mai","Juin","Juil","Août","Sept","Oct","Nov","Déc"],
  dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
  dayNamesShort: ['Dim.','Lun.','Mar.','Mer.','Jeu.','Ven.','Sam.'],
  dateFormat: "dddd, d MMMM yyyy",
  dateFormatMonth: "MMMM yyyy",
  dateFormatYear: "yyyy",
  ok: "Accepter",
  cancel: "Annuler"
};
XDate.locales['de'] = {
  monthNames: ["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],
  monthNamesShort: ["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],
  dayNames: ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],
  dayNamesShort: ["So","Mo","Di","Mi","Do","Fr","Sa"],
  dateFormat: "dddd, d.MMMM yyyy",
  dateFormatMonth: "MMMM yyyy",
  dateFormatYear: "yyyy",
  ok: "\u00dcbernehmen",
  cancel: "Abbrechen"
};
XDate.locales['en'] = {
  monthNames: ["January","February","March","April","May","June","July","August","September","October","November","December"],
  monthNamesShort: ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
  dayNames: ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
  dayNamesShort: ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],
  dateFormat: "dddd, MMMM d, yyyy",
  dateFormatMonth: "MMMM yyyy",
  dateFormatYear: "yyyy",
  ok: "Accept",
  cancel: "Cancel"
};
XDate.locales['he'] = {
  monthNames: ["ינואר","פברואר","מרץ","אפריל","מאי","יוני","יולי","אוגוסט","ספטמבר","אוקטובר","נובמבר","דצמבר"],
  monthNamesShort: ["ינואר","פבר","מרץ","אפר","מאי","יוני","יולי","אוג","ספט","אוק","נוב","דצ"],
  dayNames: ["ראשון","שני","שלישי","רביעי","חמישי","שישי","שבת"],
  dayNamesShort: ["א","ב","ג","ד","ה","ו","שבת"],
  dateFormat: "dddd, MMMM d, yyyy",
  dateFormatMonth: "MMMM yyyy",
  dateFormatYear: "yyyy",
  ok: "אישור",
  cancel: "ביטול"
};
XDate.locales['it'] = {
  monthNames: ["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],
  monthNamesShort: ["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],
  dayNames: ["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],
  dayNamesShort: ["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],
  dateFormat: "dddd, d MMMM yyyy",
  dateFormatMonth: "MMMM yyyy",
  dateFormatYear: "yyyy",
  ok: "Assumi",
  cancel: "Annulla"
};
XDate.locales['es'] = {
  monthNames: ["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],
  monthNamesShort: ["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],
  dayNames: ["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],
  dayNamesShort: ["Dom","Lun","Mar","Mié","Jue","Vie","Sáb"],
  dateFormat: "dddd, d 'de' MMMM 'de' yyyy",
  dateFormatMonth: "MMMM 'de' yyyy",
  dateFormatYear: "yyyy",
  ok: "Aplicar",
  cancel: "Cancelar"
};
XDate.locales['nl'] = {
  monthNames: ["Januari","Februari","Maart","April","Mei","Juni","Juli","Augustus","September","Oktober","November","December"],
  monthNamesShort: ["Jan","Feb","Mrt","Apr","Mei","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],
  dayNames: ["Zondag","Maandag","Dinsdag","Woensdag","Donderdag","Vrijdag","Zaterdag"],
  dayNamesShort: ["Zo","Ma","Di","Wo","Do","Vr","Za"],
  dateFormat: "dddd, d MMMM yyyy",
  dateFormatMonth: "MMMM yyyy",
  dateFormatYear: "yyyy",
  ok: "Overnemen",
  cancel: "Annuleren"
};
XDate.locales['sv'] = {
  monthNames: ["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],
  monthNamesShort: ["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],
  dayNames: ["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],
  dayNamesShort: ["Sö","Må","Ti","On","To","Fr","Lö"],
  dateFormat: "dddd, d MMMM yyyy",
  dateFormatMonth: "MMMM yyyy",
  dateFormatYear: "yyyy",
  ok: "Klar",
  cancel: "Avbryt"
};<|endoftext|>


<commit_before>

/**
 * @param {number|string} sizeOrData
 * @param {string=} opt_encoding
 * @constructor
 */
var Buffer = function(sizeOrData, opt_encoding) {};

/**
 * @param {string} string
 * @return {number}
 */
Buffer.byteLength = function(string) {};

/**
 * @type {number}
 */
Buffer.prototype.length = 0;

/**
 * @param {number=} opt_start
 * @param {number=} opt_end
 * @return {!Buffer}
 */
Buffer.prototype.slice = function(opt_start, opt_end) {};

/**
 *
 * @param {string} string
 * @param {number=} opt_offset
 * @param {number=} opt_length
 * @param {string=} opt_encoding
 */
Buffer.prototype.write = function(string, opt_offset, opt_length, opt_encoding) {};

/**
 * @param {!Buffer} target
 * @param {number=} opt_targetStart
 * @param {number=} opt_sourceStart
 * @param {number=} opt_sourceEnd
 */
Buffer.prototype.copy =
  function(target, opt_targetStart, opt_sourceStart, opt_sourceEnd) {};

/**
 * @param {string=} opt_encoding
 * @return {string}
 */
Buffer.prototype.toString = function(opt_encoding) {};

/**
 * @param {!Array.<!Buffer>} list
 * @param {number=} opt_totalLength
 * @return !Buffer
 */
Buffer.concat = function(list, opt_totalLength) {};
<commit_msg>buffer tostring externs<commit_after>

/**
 * @param {number|string} sizeOrData
 * @param {string=} opt_encoding
 * @constructor
 */
var Buffer = function(sizeOrData, opt_encoding) {};

/**
 * @param {string} string
 * @return {number}
 */
Buffer.byteLength = function(string) {};

/**
 * @type {number}
 */
Buffer.prototype.length = 0;

/**
 * @param {number=} opt_start
 * @param {number=} opt_end
 * @return {!Buffer}
 */
Buffer.prototype.slice = function(opt_start, opt_end) {};

/**
 *
 * @param {string} string
 * @param {number=} opt_offset
 * @param {number=} opt_length
 * @param {string=} opt_encoding
 */
Buffer.prototype.write = function(string, opt_offset, opt_length, opt_encoding) {};

/**
 * @param {!Buffer} target
 * @param {number=} opt_targetStart
 * @param {number=} opt_sourceStart
 * @param {number=} opt_sourceEnd
 */
Buffer.prototype.copy =
  function(target, opt_targetStart, opt_sourceStart, opt_sourceEnd) {};

/**
 * @param {string=} opt_encoding
 * @param {number=} opt_start
 * @param {number=} opt_end
 * @return {string}
 */
Buffer.prototype.toString = function(opt_encoding, opt_start, opt_end) {};

/**
 * @param {!Array.<!Buffer>} list
 * @param {number=} opt_totalLength
 * @return !Buffer
 */
Buffer.concat = function(list, opt_totalLength) {};
<|endoftext|>


<commit_before>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal2 from 'terminal.js';
import key from '../../utils/keyboard';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    login: false,
  };
  constructor(_config) {
    super();
    const config = {...defaultConfig, ..._config};

    this._parser = config.parser;
    this._term2 = new Terminal2(config.terminal);
    this._state = { ...Bot.initialState };

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.onconnect = this.emit.bind(this, 'connect');
    socket.onmessage = this.emit.bind(this, 'message');
    socket.connect();

    this.on('message', (msg) => {
      this._term2.write(msg);
      this.emit('redraw', this._term2.toString());
    });
    this._socket = socket;
  }

  get state() {
    return {...this._state};
  }

  async send(msg) {
    return new Promise(resolve => {
      this._socket.send(msg);
      this.once('message', msg => {
        resolve(msg);
      });
    });
  }

  async login(username, password) {
    if (this._state.login) return;
    await this.send(`${username}${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
    }
    return ret;
  }

  _checkLogin() {
    const getLine = this._term2.state.getLine.bind(this._term2.state);
    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      this.send(` `);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      this.send(`y${key.Enter}`);
    } else if (getLine(0).str.includes("主功能表")) {
      this.emit('login.success');
      return true;
    } else {
      this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`$$${offset}${key.Enter}`);
    }
    const getLine = this._term2.state.getLine.bind(this._term2.state);
    let articles = [];
    for(let i=3; i<=22; i++) {
      let line = getLine(i).str;
      let delta = (line[16] == " ") ? 0 : 1;
      line = line.slice(0, 7) + ' '.repeat(delta) + line.slice(7);
      articles.push({
        sn: line.slice(0, 7).trim(),
        push: line.slice(9, 11).trim(),
        date: line.slice(11, 16).trim(),
        author: line.slice(17, 29).trim(),
        status: line.slice(30, 32).trim(),
        title: line.slice(32).trim()
      });
    }
    return articles;
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const getLine = this._term2.state.getLine.bind(this._term2.state);

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author: getLine(0).str.slice(5, 50).trim(),
      title: getLine(1).str.slice(5).trim(),
      timestamp: getLine(2).str.slice(5).trim(),
      lines: [],
    };

    do {
      for(let i=0; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    } while (!getLine(23).str.includes("100%"));

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.send(key.ArrowLeft);

    return article;
  }

  async enterBoard(boardname) {
    await this.send(`s${boardname}${key.Enter} ${key.End}`);
    boardname = boardname.toLowerCase();
    const getLine = this._term2.state.getLine.bind(this._term2.state);

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      return true;
    }
    return false;
  }
}

export default Bot;
<commit_msg>async checkLogin<commit_after>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal2 from 'terminal.js';
import key from '../../utils/keyboard';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    login: false,
  };
  constructor(_config) {
    super();
    const config = {...defaultConfig, ..._config};

    this._parser = config.parser;
    this._term2 = new Terminal2(config.terminal);
    this._state = { ...Bot.initialState };

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.onconnect = this.emit.bind(this, 'connect');
    socket.onmessage = this.emit.bind(this, 'message');
    socket.connect();

    this.on('message', (msg) => {
      this._term2.write(msg);
      this.emit('redraw', this._term2.toString());
    });
    this._socket = socket;
  }

  get state() {
    return {...this._state};
  }

  async send(msg) {
    return new Promise(resolve => {
      this._socket.send(msg);
      this.once('message', msg => {
        resolve(msg);
      });
    });
  }

  async login(username, password) {
    if (this._state.login) return;
    await this.send(`${username}${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = await this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
      state.position = {
        boardname: "",
      };
    }
    return ret;
  }

  async _checkLogin() {
    const getLine = this._term2.state.getLine.bind(this._term2.state);
    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(0).str.includes("主功能表")) {
      this.emit('login.success');
      return true;
    } else {
      await this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`$$${offset}${key.Enter}`);
    }
    const getLine = this._term2.state.getLine.bind(this._term2.state);
    let articles = [];
    for(let i=3; i<=22; i++) {
      let line = getLine(i).str;
      let delta = (line[16] == " ") ? 0 : 1;
      line = line.slice(0, 7) + ' '.repeat(delta) + line.slice(7);
      articles.push({
        sn: line.slice(0, 7).trim(),
        push: line.slice(9, 11).trim(),
        date: line.slice(11, 16).trim(),
        author: line.slice(17, 29).trim(),
        status: line.slice(30, 32).trim(),
        title: line.slice(32).trim()
      });
    }
    return articles;
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const getLine = this._term2.state.getLine.bind(this._term2.state);

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author: getLine(0).str.slice(5, 50).trim(),
      title: getLine(1).str.slice(5).trim(),
      timestamp: getLine(2).str.slice(5).trim(),
      lines: [],
    };

    do {
      for(let i=0; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    } while (!getLine(23).str.includes("100%"));

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.send(key.ArrowLeft);

    return article;
  }

  async enterBoard(boardname) {
    if (this.state.position.boardname.toLowerCase() === boardname.toLowerCase())
      return true;
    await this.send(`s${boardname}${key.Enter} ${key.End}`);
    boardname = boardname.toLowerCase();
    const getLine = this._term2.state.getLine.bind(this._term2.state);

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      this._state.position.boardname = boardname;
      return true;
    }
    return false;
  }
}

export default Bot;
<|endoftext|>


<commit_before>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal2 from 'terminal.js-wcwidth';

import key from '../../utils/keyboard';
import {
  getWidth,
  indexOfWidth,
  substrWidth,
} from '../../utils/char';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    login: false,
  };
  static forwardEvents = [
    'connect',
    'disconnect',
    'message',
    'error',
  ];
  constructor(config) {
    super();
    config = {...defaultConfig, ...config};

    this._parser = config.parser;
    this._term = new Terminal2(config.terminal);
    this._state = { ...Bot.initialState };
    this._term.state.setMode('stringWidth', 'dbcs');

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.connect();

    Bot.forwardEvents.forEach(e => {
      socket.on(e, this.emit.bind(this, e));
    });
    socket.on('message', (msg) => {
      this._term.write(msg);
      this.emit('redraw', this._term.toString());
    });
    this._socket = socket;
  }

  get state() {
    return {...this._state};
  }

  getLine = (n) => {
    return this._term.state.getLine(n);
  };

  async send(msg) {
    return new Promise(resolve => {
      this._socket.send(msg);
      this.once('message', msg => {
        resolve(msg);
      });
    });
  }

  async login(username, password) {
    if (this._state.login) return;
    await this.send(`${username}${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = await this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
      state.position = {
        boardname: "",
      };
      this.emit('stateChange', this.state);
    }
    return ret;
  }

  async _checkLogin() {
    const { getLine } = this;
    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(0).str.includes("主功能表")) {
      this.emit('login.success');
      return true;
    } else {
      await this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`$$${offset}${key.Enter}`);
    }
    const { getLine } = this;
    let articles = [];
    for(let i=3; i<=22; i++) {
      let line = getLine(i).str;
      articles.push({
        sn:     substrWidth('dbcs', line, 0,   7).trim(),
        push:   substrWidth('dbcs', line, 9,   2).trim(),
        date:   substrWidth('dbcs', line, 11,  5).trim(),
        author: substrWidth('dbcs', line, 17, 12).trim(),
        status: substrWidth('dbcs', line, 30,  2).trim(),
        title:  substrWidth('dbcs', line, 32    ).trim(),
      });
    }
    return articles;
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const { getLine } = this;

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author:    substrWidth('dbcs', getLine(0).str, 7, 50).trim(),
      title:     substrWidth('dbcs', getLine(1).str, 7    ).trim(),
      timestamp: substrWidth('dbcs', getLine(2).str, 7    ).trim(),
      lines: [],
    };

    article.lines.push(getLine(0).str);

    do {
      for(let i=1; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    } while (!getLine(23).str.includes("100%"));

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.send(key.ArrowLeft);

    return article;
  }

  async enterBoard(boardname) {
    if (this.state.position.boardname.toLowerCase() === boardname.toLowerCase())
      return true;
    await this.send(`s${boardname}${key.Enter} ${key.Home}${key.End}`);
    boardname = boardname.toLowerCase();
    const getLine = this._term.state.getLine.bind(this._term.state);

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      this._state.position.boardname = boardname;
      this.emit('stateChange', this.state);
      return true;
    }
    return false;
  }
}

export default Bot;
<commit_msg>add getFavorite function<commit_after>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal2 from 'terminal.js-wcwidth';

import key from '../../utils/keyboard';
import {
  getWidth,
  indexOfWidth,
  substrWidth,
} from '../../utils/char';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    login: false,
  };
  static forwardEvents = [
    'connect',
    'disconnect',
    'message',
    'error',
  ];
  constructor(config) {
    super();
    config = {...defaultConfig, ...config};

    this._parser = config.parser;
    this._term = new Terminal2(config.terminal);
    this._state = { ...Bot.initialState };
    this._term.state.setMode('stringWidth', 'dbcs');

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.connect();

    Bot.forwardEvents.forEach(e => {
      socket.on(e, this.emit.bind(this, e));
    });
    socket.on('message', (msg) => {
      this._term.write(msg);
      this.emit('redraw', this._term.toString());
    });
    this._socket = socket;
  }

  get state() {
    return {...this._state};
  }

  getLine = (n) => {
    return this._term.state.getLine(n);
  };

  async send(msg) {
    return new Promise(resolve => {
      this._socket.send(msg);
      this.once('message', msg => {
        resolve(msg);
      });
    });
  }

  async login(username, password) {
    if (this._state.login) return;
    await this.send(`${username}${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = await this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
      state.position = {
        boardname: "",
      };
      this.emit('stateChange', this.state);
    }
    return ret;
  }

  async _checkLogin() {
    const { getLine } = this;
    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(0).str.includes("主功能表")) {
      this.emit('login.success');
      return true;
    } else {
      await this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`$$${offset}${key.Enter}`);
    }
    const { getLine } = this;
    let articles = [];
    for(let i=3; i<=22; i++) {
      let line = getLine(i).str;
      articles.push({
        sn:     substrWidth('dbcs', line, 0,   7).trim(),
        push:   substrWidth('dbcs', line, 9,   2).trim(),
        date:   substrWidth('dbcs', line, 11,  5).trim(),
        author: substrWidth('dbcs', line, 17, 12).trim(),
        status: substrWidth('dbcs', line, 30,  2).trim(),
        title:  substrWidth('dbcs', line, 32    ).trim(),
      });
    }
    return articles;
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const { getLine } = this;

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author:    substrWidth('dbcs', getLine(0).str, 7, 50).trim(),
      title:     substrWidth('dbcs', getLine(1).str, 7    ).trim(),
      timestamp: substrWidth('dbcs', getLine(2).str, 7    ).trim(),
      lines: [],
    };

    article.lines.push(getLine(0).str);

    do {
      for(let i=1; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    } while (!getLine(23).str.includes("100%"));

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.send(key.ArrowLeft);

    return article;
  }

  async getFavorite() {
    await this.enterFavorite();
    const { getLine } = this;

    const favorites = [];

    for(let i=3; i<23; i++) {
      let line = getLine(i).str;
      if (line.trim() === '') break;
      let favorite = {
        bn:        substrWidth('dbcs', line,  3,  4).trim() | 0,
        read:      substrWidth('dbcs', line,  8,  2).trim() === '',
        boardname: substrWidth('dbcs', line, 10, 12).trim(),
        category:  substrWidth('dbcs', line, 23,  4).trim(),
        title:     substrWidth('dbcs', line, 30, 31),
        users:     substrWidth('dbcs', line, 62,  5).trim(),
        admin:     substrWidth('dbcs', line, 67    ).trim(),
        folder:    false,
        divider:   false,
      };
      switch (favorite.boardname) {
        case 'MyFavFolder':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            folder: true,
          };
          break;
        case '------------':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            divider: true,
          };
          break;
        default:
          break;
      }
      favorites.push(favorite);
    }
    return favorites;
  }

  async enter() {
    await this.send(`${key.ArrowLeft.repeat(10)}`);
    return true;
  }

  async enterBoard(boardname) {
    if (this.state.position.boardname.toLowerCase() === boardname.toLowerCase())
      return true;
    await this.send(`s${boardname}${key.Enter} ${key.Home}${key.End}`);
    boardname = boardname.toLowerCase();
    const { getLine } = this;

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      this._state.position.boardname = boardname;
      this.emit('stateChange', this.state);
      return true;
    }
    return false;
  }

  async enterFavorite() {
    await this.enter();
    await this.send(`F${key.Enter}`);
    return true;
  }
}

export default Bot;
<|endoftext|>


<commit_before>'use strict';

angular.module('w11k.slides').factory('SlidesService', ['slidesConfig', '$location', '$rootScope', function (slidesConfig, $location, $rootScope) {
  var activeSlide;

  function activateFirstSlide() {
    if (angular.isDefined(activeSlide)) {
      activeSlide.active = false;
    }
    activeSlide = slides[0];
    activeSlide.active = true;
  }

  var slides;
  var slidesMap = {};

  $rootScope.$on('$locationChangeSuccess', function () {
    var slideName = $location.path();

    if (slideName.substring(0, 1) === '/') {
      slideName = slideName.substring(1, slideName.length);
    }

    var slide =  slidesMap[slideName];

    if (angular.isDefined(slide)) {
      slide.activate();
    }
  });

  function mapSlidesConfig() {
    var prefix = slidesConfig.slideTemplatePrefix || 'slides/content/';
    var suffix = slidesConfig.slideTemplateSuffix || '.tpl.html';

    slides = slidesConfig.slides.map(function (slide) {
      var slideObject =  {
        name: slide,
        template: prefix + slide + suffix,
        active: false
      };

      slideObject.activate = function () {
        activeSlide.active = false;
        activeSlide = this;

        slideObject.active = true;
      };

      return slideObject;
    });

    for (var i = 0; i < slides.length; i++) {
      var element = slides[i];
      var previous;
      if (i > 0) {
        previous = slides[i - 1];
      }
      else {
        previous = slides[slides.length - 1];
      }

      var next;
      if (i + 1 < slides.length) {
        next = slides[i + 1];
      }
      else {
        next = slides[0];
      }

      element.previous = previous;
      element.next = next;

      slidesMap[element.name] = element;
    }
  }

  mapSlidesConfig();
  activateFirstSlide();

  return {
    getActiveSlide: function () { return activeSlide; },
    getSlides: function () { return slides; },
    navigateTo: function (slideName) {
      $location.path(slideName);
    },
    navigateToFirst: function () {
      $location.path(slides[0].name);
    },
    navigateToLast: function () {
      $location.path(slides[slides.length - 1].name);
    },
    navigateToOverview: function () {
      $location.path(slides[1].name);
    }
  };
}]);

angular.module('w11k.slides').controller('SlidesCtrl', ['$scope', 'SlidesService', function ($scope, SlidesService) {
  $scope.slides = SlidesService.getSlides();
}]);

angular.module('w11k.slides').directive('w11kSlideMaster', ['slidesConfig', function (slidesConfig) {
  var removeChildren = function (node) {
    var last = node.lastChild;
    if (last) {
      do {
        node.removeChild(last);
        last = node.lastChild;
      } while (last);
    }
  };

  return {
    templateUrl: function (element, attrs) {
      var key = attrs.w11kSlideMaster || attrs.master;
      var templateUrl = slidesConfig.masters[key];

      if (angular.isUndefined(templateUrl)) {
        throw new Error('No Mater-Slide found for "' + key + '". Please configure "slidesConfig" properly.');
      }

      return templateUrl;
    },
    restrict: 'EA',
    replace: true,
    transclude: true,
    link: function (scope, iElement, iAttrs, ctrl, transclude) {
      var transclusionScope;

      transclude(function(clone, scope) {

        for (var i = 0; i < clone.length; i++) {
          var part = clone[i];

          if (part !== undefined && angular.isFunction(part.getAttribute)) {
            var partName = part.getAttribute('w11k-slide-part-source');

            if (partName !== undefined && partName !== null) {
              var selector = '[w11k-slide-part-target="' + partName + '"]';
              var container = iElement[0].querySelector(selector);
              if (container !== null) {
                removeChildren(container);
                container.appendChild(part, container);
                container.removeAttribute('w11k-slide-part-target');
              }
            }
          }
        }

        transclusionScope = scope;
      });

      scope.$on('$destroy', function () {
        transclusionScope.$destroy();
      });
    }
  };
}]);

angular.module('w11k.slides').directive('w11kSlides', [
  '$location', '$window', '$document', 'SlidesService', '$rootScope', 'slidesConfig',
  function ($location, $window, $document, SlidesService, $rootScope, slidesConfig) {
    return {
      restrict: 'EA',
      templateUrl: slidesConfig.directiveTemplateUrl || 'slides/slides.tpl.html',
      replace: true,
      link: function (scope, element) {

        var goToNext = function () {
          var next = SlidesService.getActiveSlide().next;
          if (angular.isDefined(next)) {
            SlidesService.navigateTo(next.name);
          }
        };

        var goToPrevious = function () {
          var previous = SlidesService.getActiveSlide().previous;
          if (angular.isDefined(previous)) {
            SlidesService.navigateTo(previous.name);
          }
        };

        var localStorageModeKey = 'w11k-slides.mode';
        var mode = 'export';

        function toggleMode() {
          if (mode === 'export') {
            mode = 'screen';
          }
          else if (mode === 'screen') {
            mode = 'export';
          }

          setMode(mode);

          if (angular.isDefined($window.localStorage)) {
            $window.localStorage[localStorageModeKey] = mode;
          }
        }

        function setMode(mode) {
          if (mode === 'export') {
            element.removeClass('screen');
            element.addClass('export');
          }
          else if (mode === 'screen') {
            element.removeClass('export');
            element.addClass('screen');
          }
        }

        function toggleOverlay() {
          element[0].querySelector('div.overlay').classList.toggle('active');
        }

        if (angular.isDefined($window.localStorage)) {
          if (angular.isDefined($window.localStorage[localStorageModeKey])) {
            mode = $window.localStorage[localStorageModeKey];
            setMode(mode);
          }
        }

        $document.bind('keydown', function (event) {
          var action;

          if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) {
            return;
          }

          // right or page down
          if (event.keyCode === 39 || event.keyCode === 34) {
            action = goToNext;
          }
          // left or page up
          else if (event.keyCode === 37 || event.keyCode === 33) {
            action = goToPrevious;
          }
          // pos 1
          else if (event.keyCode === 36) {
            action = SlidesService.navigateToFirst;
          }
          // end
          else if (event.keyCode === 35) {
            action = SlidesService.navigateToLast;
          }
          // o
          else if (event.keyCode === 79) {
            action = SlidesService.navigateToOverview;
          }
          // e
          else if (event.keyCode === 69) {
            action = toggleMode;
          }
          // p or period
          else if (event.keyCode === 80 || event.keyCode === 190) {
            action = toggleOverlay;
          }

          if (action) {
            $rootScope.$apply(function () {
              action();
            });
          }
        });
      }
    };
  }
]);
<commit_msg>fix(slides): scroll to top on navigating to other slide<commit_after>'use strict';

angular.module('w11k.slides').factory('SlidesService', ['slidesConfig', '$location', '$rootScope', function (slidesConfig, $location, $rootScope) {
  var activeSlide;

  function activateFirstSlide() {
    if (angular.isDefined(activeSlide)) {
      activeSlide.active = false;
    }
    activeSlide = slides[0];
    activeSlide.active = true;
  }

  var slides;
  var slidesMap = {};

  $rootScope.$on('$locationChangeSuccess', function () {
    var slideName = $location.path();

    if (slideName.substring(0, 1) === '/') {
      slideName = slideName.substring(1, slideName.length);
    }

    var slide =  slidesMap[slideName];

    if (angular.isDefined(slide)) {
      slide.activate();
    }
  });

  function mapSlidesConfig() {
    var prefix = slidesConfig.slideTemplatePrefix || 'slides/content/';
    var suffix = slidesConfig.slideTemplateSuffix || '.tpl.html';

    slides = slidesConfig.slides.map(function (slide) {
      var slideObject =  {
        name: slide,
        template: prefix + slide + suffix,
        active: false
      };

      slideObject.activate = function () {
        activeSlide.active = false;
        activeSlide = this;

        slideObject.active = true;
      };

      return slideObject;
    });

    for (var i = 0; i < slides.length; i++) {
      var element = slides[i];
      var previous;
      if (i > 0) {
        previous = slides[i - 1];
      }
      else {
        previous = slides[slides.length - 1];
      }

      var next;
      if (i + 1 < slides.length) {
        next = slides[i + 1];
      }
      else {
        next = slides[0];
      }

      element.previous = previous;
      element.next = next;

      slidesMap[element.name] = element;
    }
  }

  mapSlidesConfig();
  activateFirstSlide();

  return {
    getActiveSlide: function () { return activeSlide; },
    getSlides: function () { return slides; },
    navigateTo: function (slideName) {
      $location.path(slideName);
    },
    navigateToFirst: function () {
      $location.path(slides[0].name);
    },
    navigateToLast: function () {
      $location.path(slides[slides.length - 1].name);
    },
    navigateToOverview: function () {
      $location.path(slides[1].name);
    }
  };
}]);

angular.module('w11k.slides').controller('SlidesCtrl', ['$scope', 'SlidesService', function ($scope, SlidesService) {
  $scope.slides = SlidesService.getSlides();
}]);

angular.module('w11k.slides').directive('w11kSlideMaster', ['slidesConfig', function (slidesConfig) {
  var removeChildren = function (node) {
    var last = node.lastChild;
    if (last) {
      do {
        node.removeChild(last);
        last = node.lastChild;
      } while (last);
    }
  };

  return {
    templateUrl: function (element, attrs) {
      var key = attrs.w11kSlideMaster || attrs.master;
      var templateUrl = slidesConfig.masters[key];

      if (angular.isUndefined(templateUrl)) {
        throw new Error('No Mater-Slide found for "' + key + '". Please configure "slidesConfig" properly.');
      }

      return templateUrl;
    },
    restrict: 'EA',
    replace: true,
    transclude: true,
    link: function (scope, iElement, iAttrs, ctrl, transclude) {
      var transclusionScope;

      transclude(function(clone, scope) {

        for (var i = 0; i < clone.length; i++) {
          var part = clone[i];

          if (part !== undefined && angular.isFunction(part.getAttribute)) {
            var partName = part.getAttribute('w11k-slide-part-source');

            if (partName !== undefined && partName !== null) {
              var selector = '[w11k-slide-part-target="' + partName + '"]';
              var container = iElement[0].querySelector(selector);
              if (container !== null) {
                removeChildren(container);
                container.appendChild(part, container);
                container.removeAttribute('w11k-slide-part-target');
              }
            }
          }
        }

        transclusionScope = scope;
      });

      scope.$on('$destroy', function () {
        transclusionScope.$destroy();
      });
    }
  };
}]);

angular.module('w11k.slides').directive('w11kSlides', [
  '$location', '$window', '$document', 'SlidesService', '$rootScope', 'slidesConfig',
  function ($location, $window, $document, SlidesService, $rootScope, slidesConfig) {
    return {
      restrict: 'EA',
      templateUrl: slidesConfig.directiveTemplateUrl || 'slides/slides.tpl.html',
      replace: true,
      link: function (scope, element) {

        var goToNext = function () {
          var next = SlidesService.getActiveSlide().next;
          if (angular.isDefined(next)) {
            SlidesService.navigateTo(next.name);
          }
        };

        var goToPrevious = function () {
          var previous = SlidesService.getActiveSlide().previous;
          if (angular.isDefined(previous)) {
            SlidesService.navigateTo(previous.name);
          }
        };

        var localStorageModeKey = 'w11k-slides.mode';
        var mode = 'export';

        function toggleMode() {
          if (mode === 'export') {
            mode = 'screen';
          }
          else if (mode === 'screen') {
            mode = 'export';
          }

          setMode(mode);

          if (angular.isDefined($window.localStorage)) {
            $window.localStorage[localStorageModeKey] = mode;
          }
        }

        function setMode(mode) {
          if (mode === 'export') {
            element.removeClass('screen');
            element.addClass('export');
          }
          else if (mode === 'screen') {
            element.removeClass('export');
            element.addClass('screen');
          }
        }

        function toggleOverlay() {
          element[0].querySelector('div.overlay').classList.toggle('active');
        }

        if (angular.isDefined($window.localStorage)) {
          if (angular.isDefined($window.localStorage[localStorageModeKey])) {
            mode = $window.localStorage[localStorageModeKey];
            setMode(mode);
          }
        }

        $document.bind('keydown', function (event) {
          var action;
          var actionType;

          if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) {
            return;
          }

          // right or page down
          if (event.keyCode === 39 || event.keyCode === 34) {
            action = goToNext;
            actionType = 'navigate';
          }
          // left or page up
          else if (event.keyCode === 37 || event.keyCode === 33) {
            action = goToPrevious;
            actionType = 'navigate';
          }
          // pos 1
          else if (event.keyCode === 36) {
            action = SlidesService.navigateToFirst;
            actionType = 'navigate';
          }
          // end
          else if (event.keyCode === 35) {
            action = SlidesService.navigateToLast;
            actionType = 'navigate';
          }
          // o
          else if (event.keyCode === 79) {
            action = SlidesService.navigateToOverview;
            actionType = 'navigate';
          }
          // e
          else if (event.keyCode === 69) {
            action = toggleMode;
          }
          // p or period
          else if (event.keyCode === 80 || event.keyCode === 190) {
            action = toggleOverlay;
          }

          if (action) {
            $rootScope.$apply(function () {
              action();
              if (actionType === 'navigate') {
                $window.scrollTo(0, 0);
              }
            });
          }
        });
      }
    };
  }
]);
<|endoftext|>


<commit_before>'use strict';

exports.copy = function (options) {
    try {
        var copy = require(`../driver/${options.driver}/copy`);
    } catch(e) {
        onError(options);
    }
    return copy(options);
};

exports.remove = function(options) {
    try {
        var remove = require(`../driver/${options.driver}/remove`);
        return copy(options);
    } catch(e) {
        onError(options);
    }
    return remove(options);
};

function onError(options) {
    throw new Error(`invalid driver option: ${options.driver}`);
}<commit_msg>fix source.remove<commit_after>'use strict';

exports.copy = function (options) {
    try {
        var copy = require(`../driver/${options.driver}/copy`);
    } catch(e) {
        onError(options);
    }
    return copy(options);
};

exports.remove = function(options) {
    try {
        var remove = require(`../driver/${options.driver}/remove`);
    } catch(e) {
        onError(options);
    }
    return remove(options);
};

function onError(options) {
    throw new Error(`invalid driver option: ${options.driver}`);
}<|endoftext|>


<commit_before>var claimer = require('role.claimer');
var optimizeWorkers = require('optimize.workers');

function countRoles(room, role) {
  return _.filter(room.find(FIND_MY_CREEPS), (creep) => creep.memory.role == role).length;
}

function getRole(room) {
  if (Memory.warTarget && countRoles(room, 'harvester') > 3) {
    return 'attacker';
  }
  return 'harvester';
  // TODO: check if we are under attack
}

function getCost(body) {
  var cost = 0;
  for (var piece of body) {
    cost += BODYPART_COST[piece];
  }
  return cost;
}

var adds = [
  [WORK, CARRY, MOVE],
  [WORK, MOVE],
  [CARRY, MOVE],
  [MOVE]
];

function attackBody() {
  return [ATTACK,ATTACK,ATTACK,MOVE,MOVE,MOVE];
}

function getBody(role, energy) {
  if (role === 'attacker') {
    return attackBody();
  }
  var base = [WORK, CARRY, MOVE];
  var cost = getCost(base);

  while (cost < energy && base.length < MAX_CREEP_SIZE) {
    var remaining = energy - cost;
    var added = false;

    // add the largest chunk we can, bail if there isn't enough energy left
    for (var i in adds) {
      var toAdd = adds[i];
      if (getCost(toAdd) < remaining && (toAdd.length + base.length) <= MAX_CREEP_SIZE) {
        base = base.concat(toAdd);
        cost = getCost(base);
        added = true;
        break;
      }
    }

    if (!added) {
      break;
    }
  }

  // now remove any extra MOVE body parts
  var numMoves = _.filter(base, b => b === MOVE).length;
  var movesRequired = Math.ceil((base.length - numMoves) / 2);
  for (var i in base) {
    if (numMoves <= movesRequired) break;
    if (base[i] === MOVE) {
      base[i] = null;
      numMoves -= 1;
    }
  }

  return _.filter(base, b => b != null);
}

function spawnCreep(spawn, role, energy) {
  var body = getBody(role, energy);
  var cost = getCost(body);
  if (spawn.canCreateCreep(body) === OK) {
    spawn.room.log('Spawning a ' + role + ' using ' + cost + ' energy ');
    spawn.createCreep(body, undefined, {role: role});
  }
}

function switchRole(room, count, from, to) {
  var fromCreeps = room.find(FIND_MY_CREEPS, {
    filter: (creep) => creep.memory.role === from
  });
  for (var i = 0; i < count && i < fromCreeps.length; i++) {
    var toSwitch = fromCreeps[i];
    toSwitch.memory.role = to;
    room.log('Switched ' + toSwitch.name + ' to ' + to);
  }
}

function run() {
  optimizeWorkers.initMemory();
  optimizeWorkers.checkEnergy();

  for (var name in Game.spawns) {
    var spawn = Game.spawns[name];
    var room = spawn.room;

    var creeps = room.find(FIND_MY_CREEPS);
    var numCreeps = countRoles(room, 'harvester');
    var noCreeps = numCreeps === 0 && room.energyAvailable >= 300;
    var tooManyCreeps = numCreeps >= optimizeWorkers.getMaxCreeps(room.name);
    var maxEnergy = room.energyAvailable === room.energyCapacityAvailable;
    var usingFatCreeps = _.filter(creeps, c => c.memory.cost > room.energyCapacityAvailable).length > 0;
    // TODO: handle energy in multiple spawns?

    var role = getRole(spawn.room);
    var wartime = role === 'attacker' && room.energyAvailable >= getCost(attackBody());

    var spawnExpander = !noCreeps && countRoles(room, 'claimer') === 0 && claimer.shouldExpand(room);
    var extractors = room.find(FIND_MY_STRUCTURES, {
      filter: s => s.structureType === STRUCTURE_EXTRACTOR
    });
    if (spawnExpander) {
      claimer.spawnExpander(spawn);
    } else if (wartime || (!tooManyCreeps && !usingFatCreeps && (maxEnergy || noCreeps))) {
      if (wartime) {
        room.log("Wartime! Making a soldier");
      }
      spawnCreep(spawn, role, room.energyAvailable);
    } else if (extractors.length > 0 && countRoles(room, 'miner') === 0) {
      spawnCreep(spawn, 'miner', room.energyAvailable);
    }
  }
}

module.exports = {
  run: run,
  getCost: getCost,
  countRoles: countRoles
};
<commit_msg>don't spawn miner when there are no minerals<commit_after>var claimer = require('role.claimer');
var optimizeWorkers = require('optimize.workers');

function countRoles(room, role) {
  return _.filter(room.find(FIND_MY_CREEPS), (creep) => creep.memory.role == role).length;
}

function getRole(room) {
  if (Memory.warTarget && countRoles(room, 'harvester') > 3) {
    return 'attacker';
  }
  return 'harvester';
  // TODO: check if we are under attack
}

function getCost(body) {
  var cost = 0;
  for (var piece of body) {
    cost += BODYPART_COST[piece];
  }
  return cost;
}

var adds = [
  [WORK, CARRY, MOVE],
  [WORK, MOVE],
  [CARRY, MOVE],
  [MOVE]
];

function attackBody() {
  return [ATTACK,ATTACK,ATTACK,MOVE,MOVE,MOVE];
}

function getBody(role, energy) {
  if (role === 'attacker') {
    return attackBody();
  }
  var base = [WORK, CARRY, MOVE];
  var cost = getCost(base);

  while (cost < energy && base.length < MAX_CREEP_SIZE) {
    var remaining = energy - cost;
    var added = false;

    // add the largest chunk we can, bail if there isn't enough energy left
    for (var i in adds) {
      var toAdd = adds[i];
      if (getCost(toAdd) < remaining && (toAdd.length + base.length) <= MAX_CREEP_SIZE) {
        base = base.concat(toAdd);
        cost = getCost(base);
        added = true;
        break;
      }
    }

    if (!added) {
      break;
    }
  }

  // now remove any extra MOVE body parts
  var numMoves = _.filter(base, b => b === MOVE).length;
  var movesRequired = Math.ceil((base.length - numMoves) / 2);
  for (var i in base) {
    if (numMoves <= movesRequired) break;
    if (base[i] === MOVE) {
      base[i] = null;
      numMoves -= 1;
    }
  }

  return _.filter(base, b => b != null);
}

function spawnCreep(spawn, role, energy) {
  var body = getBody(role, energy);
  var cost = getCost(body);
  if (spawn.canCreateCreep(body) === OK) {
    spawn.room.log('Spawning a ' + role + ' using ' + cost + ' energy ');
    spawn.createCreep(body, undefined, {role: role});
  }
}

function switchRole(room, count, from, to) {
  var fromCreeps = room.find(FIND_MY_CREEPS, {
    filter: (creep) => creep.memory.role === from
  });
  for (var i = 0; i < count && i < fromCreeps.length; i++) {
    var toSwitch = fromCreeps[i];
    toSwitch.memory.role = to;
    room.log('Switched ' + toSwitch.name + ' to ' + to);
  }
}

function run() {
  optimizeWorkers.initMemory();
  optimizeWorkers.checkEnergy();

  for (var name in Game.spawns) {
    var spawn = Game.spawns[name];
    var room = spawn.room;

    var creeps = room.find(FIND_MY_CREEPS);
    var numCreeps = countRoles(room, 'harvester');
    var noCreeps = numCreeps === 0 && room.energyAvailable >= 300;
    var tooManyCreeps = numCreeps >= optimizeWorkers.getMaxCreeps(room.name);
    var maxEnergy = room.energyAvailable === room.energyCapacityAvailable;
    var usingFatCreeps = _.filter(creeps, c => c.memory.cost > room.energyCapacityAvailable).length > 0;
    // TODO: handle energy in multiple spawns?

    var role = getRole(spawn.room);
    var wartime = role === 'attacker' && room.energyAvailable >= getCost(attackBody());

    var spawnExpander = !noCreeps && countRoles(room, 'claimer') === 0 && claimer.shouldExpand(room);
    var extractors = room.find(FIND_MY_STRUCTURES, {
      filter: s => s.structureType === STRUCTURE_EXTRACTOR
    });
    if (spawnExpander) {
      claimer.spawnExpander(spawn);
    } else if (wartime || (!tooManyCreeps && !usingFatCreeps && (maxEnergy || noCreeps))) {
      if (wartime) {
        room.log("Wartime! Making a soldier");
      }
      spawnCreep(spawn, role, room.energyAvailable);
    } else if (extractors.length > 0 && countRoles(room, 'miner') === 0) {
      var minerals = spawn.pos.findClosestByRange(FIND_MINERALS, {
        filter: m => m.mineralAmount > 0
      });
      if (minerals) {
        spawnCreep(spawn, 'miner', room.energyAvailable);
      }
    }
  }
}

module.exports = {
  run: run,
  getCost: getCost,
  countRoles: countRoles
};
<|endoftext|>