<commit_before>const {EventEmitter} = require('promise-events');
const GitHubApi = require('github');
const Bottleneck = require('bottleneck');
const express = require('express');
const Context = require('./context');

/**
 * The `robot` parameter available to plugins
 *
 * @property {logger} log - A logger
 */
class Robot {
  constructor({app, cache, logger, router, catchErrors} = {}) {
    this.events = new EventEmitter();
    this.app = app;
    this.cache = cache;
    this.router = router || new express.Router();
    this.log = wrapLogger(logger);
    this.catchErrors = catchErrors;
  }

  async receive(event) {
    return this.events.emit('*', event).then(() => {
      return this.events.emit(event.event, event);
    });
  }

  /**
   * Get an {@link http://expressjs.com|express} router that can be used to
   * expose HTTP endpoints
   *
   * @example
   * module.exports = robot => {
   *   // Get an express router to expose new HTTP endpoints
   *   const app = robot.route('/my-plugin');
   *
   *   // Use any middleware
   *   app.use(require('express').static(__dirname + '/public'));
   *
   *   // Add a new route
   *   app.get('/hello-world', (req, res) => {
   *     res.end('Hello World');
   *   });
   * };
   *
   * @param {string} path - the prefix for the routes
   * @returns {@link http://expressjs.com/en/4x/api.html#router|express.Router}
   */
  route(path) {
    if (path) {
      const router = new express.Router();
      this.router.use(path, router);
      return router;
    } else {
      return this.router;
    }
  }

  /**
   * Listen for [GitHub webhooks](https://developer.github.com/webhooks/),
   * which are fired for almost every significant action that users take on
   * GitHub.
   *
   * @param {string} event - the name of the [GitHub webhook
   * event](https://developer.github.com/webhooks/#events). Most events also
   * include an "action". For example, the * [`issues`](
   * https://developer.github.com/v3/activity/events/types/#issuesevent)
   * event has actions of `assigned`, `unassigned`, `labeled`, `unlabeled`,
   * `opened`, `edited`, `milestoned`, `demilestoned`, `closed`, and `reopened`.
   * Often, your bot will only care about one type of action, so you can append
   * it to the event name with a `.`, like `issues.closed`.
   *
   * @param {Robot~webhookCallback} callback - a function to call when the
   * webhook is received.
   *
   * @example
   *
   * robot.on('push', context => {
   *   // Code was just pushed.
   * });
   *
   * robot.on('issues.opened', context => {
   *   // An issue was just opened.
   * });
   */
  on(event, callback) {
    if (callback.length === 2) {
      const caller = (new Error()).stack.split('\n')[2];
      console.warn('DEPRECATED: Event callbacks now only take a single `context` argument.');
      console.warn(caller);
    }

    const [name, action] = event.split('.');

    return this.events.on(name, async event => {
      if (!action || action === event.payload.action) {
        try {
          const github = await this.auth(event.payload.installation.id);
          const context = new Context(event, github);
          await callback(context, context /* DEPRECATED: for backward compat */);
        } catch (err) {
          this.log.error({err, event});
          if (!this.catchErrors) {
            throw err;
          }
        }
      }
    });
  }

  /**
   * Authenticate and get a GitHub client that can be used to make API calls.
   *
   * You'll probably want to use `context.github` instead.
   *
   * **Note**: `robot.auth` is asynchronous, so it needs to be prefixed with a
   * [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
   * to wait for the magic to happen.
   *
   * @example
   *
   *  module.exports = function(robot) {
   *    robot.on('issues.opened', async context => {
   *      const github = await robot.auth();
   *    });
   *  };
   *
   * @param {number} [id] - ID of the installation, which can be extracted from
   * `context.payload.installation.id`. If called without this parameter, the
   * client wil authenticate [as the app](https://developer.github.com/apps/building-integrations/setting-up-and-registering-github-apps/about-authentication-options-for-github-apps/#authenticating-as-a-github-app)
   * instead of as a specific installation, which means it can only be used for
   * [app APIs](https://developer.github.com/v3/apps/).
   *
   * @returns {Promise<github>} - An authenticated GitHub API client
   * @private
   */
  async auth(id) {
    let github;

    if (id) {
      const res = await this.cache.wrap(`app:${id}:token`, () => {
        this.log.trace(`creating token for installation ${id}`);
        return this.app.createToken(id);
      }, {ttl: 60 * 60});

      github = new GitHubApi({debug: process.env.LOG_LEVEL === 'trace'});
      github.authenticate({type: 'token', token: res.data.token});
    } else {
      github = await this.app.asApp();
    }

    return probotEnhancedClient(github);
  }
}

function probotEnhancedClient(github) {
  github = rateLimitedClient(github);

  github.paginate = require('./paginate');

  return github;
}

// Hack client to only allow one request at a time with a 1s delay
// https://github.com/mikedeboer/node-github/issues/526
function rateLimitedClient(github) {
  const limiter = new Bottleneck(1, 1000);
  const oldHandler = github.handler;
  github.handler = (msg, block, callback) => {
    limiter.submit(oldHandler.bind(github), msg, block, callback);
  };
  return github;
}
<commit_msg>Reduce auth token ttl<commit_after>const {EventEmitter} = require('promise-events');
const GitHubApi = require('github');
const Bottleneck = require('bottleneck');
const express = require('express');
const Context = require('./context');

/**
 * The `robot` parameter available to plugins
 *
 * @property {logger} log - A logger
 */
class Robot {
  constructor({app, cache, logger, router, catchErrors} = {}) {
    this.events = new EventEmitter();
    this.app = app;
    this.cache = cache;
    this.router = router || new express.Router();
    this.log = wrapLogger(logger);
    this.catchErrors = catchErrors;
  }

  async receive(event) {
    return this.events.emit('*', event).then(() => {
      return this.events.emit(event.event, event);
    });
  }

  /**
   * Get an {@link http://expressjs.com|express} router that can be used to
   * expose HTTP endpoints
   *
   * @example
   * module.exports = robot => {
   *   // Get an express router to expose new HTTP endpoints
   *   const app = robot.route('/my-plugin');
   *
   *   // Use any middleware
   *   app.use(require('express').static(__dirname + '/public'));
   *
   *   // Add a new route
   *   app.get('/hello-world', (req, res) => {
   *     res.end('Hello World');
   *   });
   * };
   *
   * @param {string} path - the prefix for the routes
   * @returns {@link http://expressjs.com/en/4x/api.html#router|express.Router}
   */
  route(path) {
    if (path) {
      const router = new express.Router();
      this.router.use(path, router);
      return router;
    } else {
      return this.router;
    }
  }

  /**
   * Listen for [GitHub webhooks](https://developer.github.com/webhooks/),
   * which are fired for almost every significant action that users take on
   * GitHub.
   *
   * @param {string} event - the name of the [GitHub webhook
   * event](https://developer.github.com/webhooks/#events). Most events also
   * include an "action". For example, the * [`issues`](
   * https://developer.github.com/v3/activity/events/types/#issuesevent)
   * event has actions of `assigned`, `unassigned`, `labeled`, `unlabeled`,
   * `opened`, `edited`, `milestoned`, `demilestoned`, `closed`, and `reopened`.
   * Often, your bot will only care about one type of action, so you can append
   * it to the event name with a `.`, like `issues.closed`.
   *
   * @param {Robot~webhookCallback} callback - a function to call when the
   * webhook is received.
   *
   * @example
   *
   * robot.on('push', context => {
   *   // Code was just pushed.
   * });
   *
   * robot.on('issues.opened', context => {
   *   // An issue was just opened.
   * });
   */
  on(event, callback) {
    if (callback.length === 2) {
      const caller = (new Error()).stack.split('\n')[2];
      console.warn('DEPRECATED: Event callbacks now only take a single `context` argument.');
      console.warn(caller);
    }

    const [name, action] = event.split('.');

    return this.events.on(name, async event => {
      if (!action || action === event.payload.action) {
        try {
          const github = await this.auth(event.payload.installation.id);
          const context = new Context(event, github);
          await callback(context, context /* DEPRECATED: for backward compat */);
        } catch (err) {
          this.log.error({err, event});
          if (!this.catchErrors) {
            throw err;
          }
        }
      }
    });
  }

  /**
   * Authenticate and get a GitHub client that can be used to make API calls.
   *
   * You'll probably want to use `context.github` instead.
   *
   * **Note**: `robot.auth` is asynchronous, so it needs to be prefixed with a
   * [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
   * to wait for the magic to happen.
   *
   * @example
   *
   *  module.exports = function(robot) {
   *    robot.on('issues.opened', async context => {
   *      const github = await robot.auth();
   *    });
   *  };
   *
   * @param {number} [id] - ID of the installation, which can be extracted from
   * `context.payload.installation.id`. If called without this parameter, the
   * client wil authenticate [as the app](https://developer.github.com/apps/building-integrations/setting-up-and-registering-github-apps/about-authentication-options-for-github-apps/#authenticating-as-a-github-app)
   * instead of as a specific installation, which means it can only be used for
   * [app APIs](https://developer.github.com/v3/apps/).
   *
   * @returns {Promise<github>} - An authenticated GitHub API client
   * @private
   */
  async auth(id) {
    let github;

    if (id) {
      const res = await this.cache.wrap(`app:${id}:token`, () => {
        this.log.trace(`creating token for installation ${id}`);
        return this.app.createToken(id);
      }, {ttl: 60 * 59}); // Cache for 1 minute less than GitHub expiry

      github = new GitHubApi({debug: process.env.LOG_LEVEL === 'trace'});
      github.authenticate({type: 'token', token: res.data.token});
    } else {
      github = await this.app.asApp();
    }

    return probotEnhancedClient(github);
  }
}

function probotEnhancedClient(github) {
  github = rateLimitedClient(github);

  github.paginate = require('./paginate');

  return github;
}

// Hack client to only allow one request at a time with a 1s delay
// https://github.com/mikedeboer/node-github/issues/526
function rateLimitedClient(github) {
  const limiter = new Bottleneck(1, 1000);
  const oldHandler = github.handler;
  github.handler = (msg, block, callback) => {
    limiter.submit(oldHandler.bind(github), msg, block, callback);
  };
  return github;
}

{'fraction_non_alphanumeric': 0.14983075004453947, 'fraction_numerical': 0.0032959201852841616, 'mean_word_length': 2.8070532383858935, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 23, 'https://': 14, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '706055', 'n_tokens_mistral': 3585, 'n_tokens_neox': 3457, 'n_words': 933}