Skip to content

Utility module for handling Twitch.tv chatbot authentication.

Notifications You must be signed in to change notification settings

laddspencer/twitch-auth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 

Repository files navigation

twitch-auth

Utility module for handling Twitch.tv chatbot authentication. The authentication process is detailed here.

Check out the NPM page: https://www.npmjs.com/package/twitch-auth

Install with: npm install twitch-auth

exports

The following functions are exported by the module.

getUserAccessToken(creds, code)

Gets a user access token required for login (using tmi.js for example). This should be used once after the initial authorization step. Subsequent logins should use the token refresh process.

creds is an object structured as follows:

{
"client_id": <your client ID>,
"client_secret": <your client secret>
}

code is the OAuth 2.0 authorization code returned by Twitch.

Returns a Promise that resolves to an object parsed from the JSON returned from the server, or rejects with an error. The returned object looks like this:

{
  "access_token": "<user access token>",
  "refresh_token": "<refresh token>",
  "expires_in": <number of seconds until the token expires>,
  "scope": "<your previously listed scope(s)>",
  "token_type": "bearer"
}

refreshUserAccessToken(creds, refreshToken)

Gets a new (refreshed) user access token for login.

creds ...same as above.

refreshToken is the refresh_token returned from the server in calls to getUserAccessToken() and/or refreshUserAccessToken().

getAppAccessToken(creds)

Gets an app access token. This is not applicable to chat login, but I wrote the function before I realized that :P

creds ...same as above.

example code

This is basically what I use to launch my chat bot. It is derived from Twitch's echo/haiku sample and has been built up from there (with the twitch-auth module being refactored out of it). It uses a local Redis server to cache access/refresh tokens. Run it initially with -c <auth code> to get your first user access token. After that, it will automatically refresh tokens as necessary.

#!/usr/bin/nodejs --harmony
//----------------------------------------------------------------

'use strict';
//----------------------------------------------------------------

const process = require('process')
const minimist = require('minimist')
const fs = require('fs')
const tmi = require('tmi.js')
const redis = require('redis');
const util = require('util')

// Twitch Auth Util (tau)
const tau = require('twitch-auth')

const defaultConfigPath = 'config.json';
const defaultConfig = {
  credsPath: './creds.json',
  reconnect: true
};
//----------------------------------------------------------------

// Called every time a message comes in:
function onMessageHandler(target, context, msg, self) {
  // Ignore messages from the bot
  if (self) {
    return;
  }
  
  console.log(msg);
}

function parseArgs(argv) {
  let args = minimist(argv.slice(2));
  if (args['_'].length > 0) {
    args._.forEach((unknownOption) => {
      console.log(`I don't know what "${unknownOption}" is.`);
    });
    
    return (null);
  }
  
  return (args);
}

function getConfigPath(args, defaultPath=defaultConfigPath) {
  if ('F' in args) {
    return (args['F']);
  }
  
  return (defaultPath);
}

function getConfig(args) {
  let configPath = getConfigPath(args);

  let config = defaultConfig;
  try {
    fs.accessSync(configPath, fs.constants.F_OK | fs.constants.R_OK);
    let configString = fs.readFileSync(configPath, 'utf8');
    config = Object.assign(config, JSON.parse(configString));
  }
  catch (err) {
    console.log(err);
  }
  
  return (config);
}

function getCreds(credsPath) {
  let credString = fs.readFileSync(credsPath, 'utf8');
  return (JSON.parse(credString));
}

function redisValue(redisClient, key) {
  return (new Promise((resolve, reject) => {
    redisClient.get(key, (err, result) => {
      console.log('redis client returned: ' + result);
      if (err) {
        reject(err);
      }
      
      resolve(result);
    });
  }));
}

function accessTokenFromAuthCode(creds, code) {
  return (tau.getUserAccessToken(creds, code));
}

function accessTokenFromCache() {
  //console.log('accessTokenFromCache()');
  
  let redisClient = redis.createClient();
  let tokens = Promise.all([
    redisValue(redisClient, "access_token"),
    redisValue(redisClient, "refresh_token")])
  .then((results) => {
    //console.log('results: ' + results);
    redisClient.end();
    return ({access_token: results[0],
             refresh_token: results[1]});
  });

  return (tokens);
}

function cacheTokenResponse(response) {
  let redisClient = redis.createClient();
  return (new Promise((resolve, reject) => {
    if (('access_token' in response ) &&
        ('refresh_token' in response)) {
      redisClient.mset('access_token', response.access_token,
                       'refresh_token', response.refresh_token, (err, reply) => {
        console.log('redis client returned: ' + reply);
        
        redisClient.end();
        if (err) {
          reject(err);
        }
        
        resolve(reply);
      });
    }
  }));
}

function reconnect(context) {
  return (tau.refreshUserAccessToken(context.creds, context.tokens.refresh_token)
          .then((response) => {
            // Update the tokens in our context oject.
            context.tokens.access_token = response.access_token;
            context.tokens.refresh_token = response.refresh_token;
            return (response);})
          .then(cacheTokenResponse)
          .then((reply) => {
            // reply is useless ("OK")
            console.log("let's try to reconnect...");
            return (context);})
          .then(serve));
}

function getTwitchOptions(context) {
  let opts = {
    options: {
      clientId: context.creds.client_id,
      debug: true
    },
    identity: {
      username: context.config.tmi.username,
      password: 'oauth:' + context.tokens.access_token
    },
    channels: context.config.tmi.channels
  }
  
  return (opts);
}

function registerTwitchEventHandlers(client, context) {
  // Register our event handlers:
  client.on('message', onMessageHandler);
  client.on('disconnected', (reason) => {
    // Login authentication failed
    console.log(util.format('Disconnected: %s', reason));
    
    reconnect(context)
    .catch((err) => {
      console.log(err);
    });
  });
}

function serve(context) {
  // Create a client with our tmi options:
  let opts = getTwitchOptions(context);
  let client = new tmi.client(opts);
  
  // Connect to Twitch:
  return (client.connect()
          .then((data) => {
            console.log(JSON.stringify(data));
            
            registerTwitchEventHandlers(client, context);
          }, (err) => {
            // Reconnect is almost always necessary because auth tokens need to be refreshed often.
            console.log("It's okay, we probably just need to refresh the auth tokens.");
            
            reconnect(context)
            .catch((err) => {
              console.log('Uh oh, reconnection attempt failed. Maybe this error message will be helpful:\n' + err);
             });
          }));
}

function launch(args) {
  let config = getConfig(args);
  let creds = getCreds(config.credsPath);
  let accessToken = Promise.resolve();
  if ('c' in args) {
    let authCode = args['c'];
    accessToken = accessToken
      .then(function() {
        return (accessTokenFromAuthCode(creds, authCode));
      })
      .then((response) => {
        return (cacheTokenResponse(response));
      });
  }
  else {
    accessToken = accessToken
      .then(function() {
        return (accessTokenFromCache());
      });
  }
  
  // Inject other context.
  accessToken
  .then((tokens) => {
    let context = {
      config: config,
      creds: creds,
      tokens: tokens
    };
    
    return (context);})
  .then(serve)
  .catch((err) => {
    console.log(err);
    process.exit(1);
  });
}
//----------------------------------------------------------------

launch(parseArgs(process.argv));
//----------------------------------------------------------------

My config.json looks like this:

{
  "tmi": {
    "channels": [
      "laddspencer"
    ],
    "username": "PhantsBot"
  }
}

My creds.json looks like this (no, those are not real creds, use your own!):

{
"client_id": "4jkcd8ejjwkemvnhuewnc98ku87uyh",
"client_secret": "d9rkkijun4jfunywhqssx6456hey7u"
}

About

Utility module for handling Twitch.tv chatbot authentication.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published