FoseFx/twitch-chatbot-boilerplate-core

View on GitHub
src/core/core.ts

Summary

Maintainability
A
1 hr
Test Coverage
A
92%
/**
 * This is your entrypoint to the boilerplate-core,
 * check out the [Setup Guide](https://github.com/FoseFx/twitch-chatbot-boilerplate-core#how-does-it-work) to get started
 * @packageDocumentation
 */

import { Express } from 'express';
import { Client, Options as TmiOptions } from 'tmi.js';
import { EventEmitter } from 'events';
import {
  StartServerOptions,
  AuthData,
  BasicProfile,
} from './server/server.types';
import { loadEnvVariables } from './env';
import { startServer } from './server/server';
import { setup } from './setup';
import * as bot from './bot/bot';
import {
  setClientReadyEmitter,
  BoilerplateEventEmitter,
  AuthDataAndBasicProfile,
} from './event';

export interface InitializeOptions {
  /**
   * You can use an existing Express instance
   *
   * Note: initialize() will change the view engine to ejs
   * Note: initialize() will add the cookieParser middelware
   * */
  app?: Express;
  /**
   * Only relevant when an express app is provided using {@link InitializeOptions.app},
   * Bind passed express app to env.PORT
   * @default true
   * */
  listen?: boolean;
  /** This hook gets called before the routes are added to the express app */
  beforeRouteSetup?: (app: Express) => void;
  /**
   * Ask the streamer for more permissions,
   * you can recieve the token using the {@link InitializeObject.boilerplate | boilerplate events}
   *
   * More on scopes: https://dev.twitch.tv/docs/authentication#scopes
   *
   * @example
   * ```TypeScript
   * const { client, boilerplate } = await initialize({
   *  scopes: ['channel:read:hype_train', 'user:read:email']
   * });
   *
   * boilerplate.on('join', async ({ authData, basicProfile }) => {
   *   await sendVerificationEmail(basicProfile.email);
   *   addListenerForHypeTrain(authData.access_token);
   * })
   * ```
   *
   * */
  scopes?: string[];
  /**
   * You can override the default options the tmi.js client is initialized with using this object.
   * Your options will be merged with the default.
   * */
  tmiOptions?: TmiOptions;
}

export interface InitializeObject {
  client: Client;
  app: Express;
  /**
   * You can use this EventEmitter to listen to events like 'join' and 'leave',
   * read about it in the [Wiki](https://github.com/FoseFx/twitch-chatbot-boilerplate/wiki/Caveats#on-join-and-part)
   * */
  boilerplate: BoilerplateEventEmitter;
}

/**
 * The heart of this package,
 * read more in the [Setup Guide](https://github.com/FoseFx/twitch-chatbot-boilerplate-core#how-does-it-work)
 *
 * @example
 * ```TypeScript
 * const { initialize } = require('twitch-chatbot-boilerplate');
 *
 * async function main() {
 *     const { client } = await initialize();
 *
 *     // This is the example on the tmi.js website
 *     client.on('message', (channel, userstate, message, self) => {
 *         if (self) return;
 *         if (message.toLowerCase() === '!hello') {
 *             client.say(channel, `@${userstate.username}, heya!`);
 *         }
 *     });
 * }
 * main().catch((e) => console.error(e));
 * ```
 * @public
 */
export function initialize(
  initializeOptions: InitializeOptions = {},
): Promise<InitializeObject> {
  return new Promise((resolve, reject) => {
    loadEnvVariables(); // make sure all variables are available

    const opts: StartServerOptions = {
      eventEmitter: new BoilerplateEventEmitter(),
      host: process.env.HOST,
      port: +process.env.PORT,
      botname: process.env.BOTNAME,
      clientId: process.env.TWITCH_CLIENT_ID,
      clientSecret: process.env.TWITCH_CLIENT_SECRET,
      setupScopes: ['chat:read', 'chat:edit'],
      scopes: initializeOptions.scopes ?? [],
      beforeRouteSetup: initializeOptions.beforeRouteSetup,
      app: initializeOptions.app,
      listen: initializeOptions.listen ?? true,
      tmiOptions: initializeOptions.tmiOptions,
    };

    // after the bot is ready the "clientReady" event is fired on this one
    const clientEventEmitter = new EventEmitter();
    setClientReadyEmitter(clientEventEmitter);

    let app: Express;

    startServer(opts)
      .then((expressApp) => {
        app = expressApp;
        return setup(opts);
      })
      .then((authData) => bot.startBot(opts, initializeOptions.tmiOptions, authData))
      .catch((error) => reject(error));

    clientEventEmitter.once('clientReady', (client: Client) => {
      client.connect = () => {
        throw new Error(
          'The twitch-chatbot-boilerplate core gave you an already connected client, there is no need to call connect()',
        );
      };
      client.disconnect = () => {
        throw new Error('You should not call disconnect()');
      };

      resolve({ client, app, boilerplate: opts.eventEmitter });
    });
  });
}

export const joinChannel = bot.joinChannel;

export const leaveChannel = bot.leaveChannel;

export {
  BoilerplateEventEmitter,
  AuthDataAndBasicProfile,
  AuthData,
  BasicProfile,
};