processor.js

  1. const async = require('async');
  2. const deepFreeze = require('deep-freeze');
  3. /**
  4. * Exists while a Handler is being processed. Contains all data a
  5. * {@link Handler} and {@link Requirement} might need and gets passed to all
  6. * Handlers and Requirements as the second parameter in their
  7. * {@link Callable~callable}
  8. *
  9. * @property {object} data Data to handle
  10. * @property {Bot} bot Bot that invoked the handling
  11. * @property {Handler} handler Handler, containing its options, etc
  12. * @property {object} params Requirements' values
  13. * @property {string} apiUrl Request's passed API URL
  14. * @property {string} route Request's route
  15. * @property {this} processor The processor itself, for use when destructing
  16. */
  17. class Processor {
  18. constructor(object) {
  19. Object.assign(this, object);
  20. this.processor = this;
  21. }
  22. /**
  23. * Sends an update using the request's API URL instead of the bot's
  24. *
  25. * #### `this`
  26. *
  27. * ```
  28. * // wrong
  29. * (done, { send }) => {
  30. * send('message', { text: "doesn't work!" });
  31. * }
  32. * ```
  33. *
  34. * The reason this given example doesn't work is that send is called from out
  35. * of the Processor object. Since the send method uses the Bot stored in the
  36. * Processor (which is normally located at `this`), a ReferenceError is
  37. * thrown.
  38. *
  39. * The following way, send is executed from within the processor object.
  40. * ```
  41. * // fix: call from the processor
  42. * (done, processor) => {
  43. * processor.send('message', { text: 'works!' });
  44. * }
  45. * ```
  46. * ```
  47. * // fix: make use of that the processor object contains itself
  48. * (done, { data, processor }) => {
  49. * processor.send('message', data.text)
  50. * }
  51. * ```
  52. * As stated in {@link Callable~callable}, when a normal function is used as
  53. * the callable instead of an arrow function, `this` is bound to the
  54. * Processor object, so that can also be used.
  55. * ```
  56. * // fix: call send from `this` in a normal function
  57. * function someCallable(done, { data }) {
  58. * console.log('can still use the destructed processor to have easier' +
  59. * 'access to properties like', data);
  60. * this.send('sendMessage', { text: 'works!' })
  61. * }
  62. * ```
  63. *
  64. * @param {string} method Is appended to the API's URL
  65. * @param {object} update The update object that should be
  66. * sent
  67. * @param {Bot~response} callback Callback containing error, response
  68. * and body
  69. * @param {object} [options] Options object
  70. * @param {boolean} [options.silent] Will not log the response if true
  71. * @param {string} [options.apiUrl] Custom API URL
  72. */
  73. send(method, update, callback, options = {}) {
  74. try {
  75. this.bot.send(method, update, callback, Object.assign(options,
  76. { apiUrl: options.apiUrl || this.apiUrl }));
  77. } catch (err) {
  78. if (err instanceof ReferenceError) {
  79. throw new ReferenceError("The Bot's send method could not be called." +
  80. "Probably you didn't bind the Processor to the send method " +
  81. 'properly. See the JSDoc for Processor#send for further' +
  82. 'information.');
  83. } else {
  84. console.error(err);
  85. }
  86. }
  87. }
  88. /**
  89. * Handles the update data
  90. *
  91. * @param {Function} callback Called when Handler is done executing
  92. */
  93. handle(callback) {
  94. this.handler.prepareRequirements();
  95. this.params = {};
  96. // add parser requirement
  97. if (this.handler.parser !== null
  98. && (this.handler.parser || this.bot.parser)) {
  99. this.handler.requirements.unshift(this.handler.parser || this.bot.parser);
  100. }
  101. async.eachSeries(this.handler.requirements, (requirement, reqDone) => {
  102. // store the current requirement in the class to be accessible in the
  103. // requirement's callable
  104. this.requirement = requirement;
  105. // calls the requirement's callable
  106. // with (done, data, params, bot, options)
  107. requirement.callable.call(this, (err, reqParams, reqData) => {
  108. if (reqParams) this.params[requirement.name] = deepFreeze(reqParams);
  109. if (reqData) this.data = reqData;
  110. if (process.env.TGBOT_VERBOSE) {
  111. this.bot.log.info(`${this.bot.logPrefix} handler`,
  112. `Requirement '${requirement.name}' ` +
  113. (err ? 'failed' : 'succeeded'));
  114. }
  115. reqDone(err);
  116. }, this);
  117. }, (err) => {
  118. // gets called on done(true) or if all requirements are done
  119. // if any requirement did not pass, do not execute handler callback
  120. // else the handler itself gets finally called
  121. if (!err) this.handler.callable.call(this, callback, this);
  122. // call callback directly if one of requirements failed
  123. else callback(true);
  124. });
  125. }
  126. }
  127. module.exports = Processor;