Skip to content

Instantly share code, notes, and snippets.

@AlcaDesign
Last active June 11, 2016 11:04
Show Gist options
  • Save AlcaDesign/48f1fa4d65adb15cf9342f24ee96cec6 to your computer and use it in GitHub Desktop.
Save AlcaDesign/48f1fa4d65adb15cf9342f24ee96cec6 to your computer and use it in GitHub Desktop.
// Load tmi.js
const tmi = require('tmi.js'),
// Load the logger.
Logger = require('./Logger'),
// Load TaskLoop and TaskLoopGroup for use.
{ TaskLoop, TaskLoopGroup } = require('./TaskLoop.js');
// Override some functionality to make an easier solution to make a message
// loop.
class MessageLoop extends TaskLoop {
constructor(opts, interval) {
// Setup the logger.
let logger = new Logger('MessageLoop', { enabled: true });
// Default to an empty object if there's no options.
opts = opts || {};
// If there isn't a task already.
if(!opts.hasOwnProperty('task') && !opts.hasOwnProperty('callback')) {
opts.task = () => { };
}
// Call the main constructor.
super(opts, interval);
// Keep the original logger but make a new one for this extended class.
this.log2 = (sec, ...msg) => logger.log(sec, ...msg);
// The channel to send the message
this.message = this.opts.message || null;
// The message to send to the channel
this.channel = this.opts.channel || null;
}
// Override callTask.
callTask() {
this.log2('callTask');
// Call the original to keep track.
super.callTask();
// Send the message to the channel.
if(this.channel !== null && this.message !== null) {
this.opts.client.say(this.channel, this.message);
}
return this; // Chainable.
}
}
// Your channel.
let channel = 'channel',
// Create the client.
client = new tmi.client({
// The identity of the bot.
identity: {
// The username of the bot.
username: 'username',
// The oauth token for the bot.
password: 'oauth:password'
},
// Set the channel in the client.
channels: [channel]
}),
// Instantiate the TaskLoopGroup.
loops = new TaskLoopGroup([
// Create a MessageLoop instance and add it to the Group.
new MessageLoop({
// The message to send.
message: 'Thanks for following the channel!',
// The channel to send it to.
channel,
// The client.
client,
// The time in between each of this message.
interval: 1000 * 60 * 60, // An hour.
// Start immediately. (ish)
callImmediately: true,
// Time to wait to call immediately.
startOffset: 1000 * 60 * 5 // 5 minutes.
})
]);
// Connect to the Twitch chat servers.
client.connect();
// Ascii colors in console
function colorize(id, ...text) {
return `\u001b[${id}m${text.join()}\u001b[39m`;
}
// The brightest white text
function whiteBright(...text) {
return colorize(97, ...text);
}
// The brightest white text
function blackBright(...text) {
return colorize(90, ...text);
}
// A somewhat simple logger for our use.
class Logger {
constructor(primaryName, opts) {
// Default to an empty object if there's no options.
this.opts = opts || {};
// Whether or not it's enabled for logging. (default off)
this.enabled = this.opts.enabled || false;
// The name of the class.
this.primaryName = primaryName;
// A bright white version of the primary name.
this.pN = whiteBright(this.primaryName);
// Call the log function when the logger is instantiated
this.log();
}
// Log some informaiton.
log(secondaryName, ...message) {
if(this.enabled === false) {
return this; // Chainable.
}
// A mid-grey colored version of the secondary name.
let sN = blackBright(secondaryName);
// If there's a secondary name and some message to log.
if(secondaryName !== undefined && message.length > 0) {
// For when you're calling log in a method of primary.
if(secondaryName !== null) { // log('method', 'message here', 'and here');
console.log(` - ${this.pN}.${sN}: ${message.join(' ')}`);
}
// For when you're calling log within the constructor.
else if(secondaryName === null) { // log(null, 'message here', 'and here');
console.log(` - ${this.pN}: ${message.join(' ')}`);
}
}
else if(secondaryName !== undefined) {
// For when you're calling log within the constructor (no additional
// arguments passed.)
if(secondaryName.indexOf(' ') > -1) { // log('message here');
console.log(` - ${this.pN}: ${secondaryName}`);
}
// For when a method is first called.
else { // log('method');
console.log(` - ${this.pN}.${sN}`);
}
}
// Log the primary name.
else { // log();=
console.log(`${this.pN}`);
}
return this; // Chainable.
}
}
module.exports = Logger;
// Load the logger.
const Logger = require('./Logger');
// A base for our message looping system for reusability.
class TaskLoop {
constructor(opts, interval) {
// Setup the logger.
let logger = new Logger('TaskLoop', { enabled: true });
this.log = (sec, ...msg) => logger.log(sec, ...msg);
// If you passed a function, set as task in the options oject.
if(typeof opts === 'function') {
opts = {
task: opts
};
}
// If you passed the interval as the second arg, set in the options
// object.
if(typeof interval === 'number') {
opts.interval = interval;
this.log(null, 'passed an interval');
}
// Default to an empty object if there's no options.
this.opts = opts || {};
// Set the task function.
this.task = this.opts.task || this.opts.callback || null;
// Set the interval in milliseconds.
this.interval = this.opts.interval || this.opts.timeout ||
this.opts.wait || this.opts.time;
// Set the arguments to call the task function with as an array.
this.arguments = this.opts.arguments || this.opts.args || [];
// Set the bind for the task function.
this.bind = this.opts.bind || global;
// If it should start on instantiation.
this.stopped = this.opts.stopped || true;
// A Date object for the last time it was stopped.
this.stoppedAt = null;
// If it should immediately call the task without waiting for the loop.
this.callImmediately = this.opts.callImmediately || false;
// Delay for the first time when callImmediately is true.
this.startOffset = this.opts.startOffset || 0;
// "interval" or "timeout" as in setTimeout and setInterval depending on
// preferred functionality.
this.loopType = this.opts.loopType || 'interval';
// The ID returned by setTimeout or setInterval.
this.intervalID = null;
// A Date object for the last time that the task was called by the
// class.
this.lastCalled = null;
// A Date object for when the instance was created.
this.created = new Date();
// The task needs to be a callable function.
if(typeof this.task !== 'function') {
throw new TypeError('Task is not a function');
}
else {
this.log(null, 'task is a function');
}
// The interval needs to be a number.
if(typeof this.interval !== 'number') {
throw new TypeError('Interval is not a number');
}
// The interval needs to be a positive and non-zero number in order to
// be used.
else if(this.interval <= 0) {
throw new RangeError('Interval is not a positive, non-zero number');
}
else {
this.log(null, 'interval was a useable number');
}
// If the task should be called immediately after instantiation.
if(this.callImmediately === true) {
// If there's an initial wait time.
if(this.startOffset > 0) {
this.log(null, 'calling TaskLoop.callTask immediately after a',
this.startOffset/1000, 'second delay');
setTimeout(() => {
this.log(null, 'calling task after initial offset of a',
this.startOffset/1000, 'second delay');
// Call the task from here.
this.callTask();
// callTask will automatically call the next one for timeout
// loopType where interval needs to be started from here.
if(this.loopType === 'interval') {
this.start();
}
// Wait the offset time.
}, this.startOffset);
}
else {
this.log(null, 'calling TaskLoop.callTask immediately');
// Call the task from here.
this.callTask();
}
}
// Otherwise just start the loop.
else {
this.log(null, 'calling TaskLoop.start');
this.start();
}
}
// Create the loop
start() {
this.log('start');
// Only create the loop if it's already stopped.
if(this.stopped === false) {
this.log('start', 'not called because it\'s already running');
return this; // Chainable.
}
// A timeout type loop.
if(this.loopType === 'timeout') {
this.intervalID = setTimeout(this.callTask.bind(this),
this.interval);
this.log('start', 'it\'s a timeout type');
}
// An interval type loop.
else {
this.intervalID = setInterval(this.callTask.bind(this),
this.interval);
this.log('start', 'it\'s a interval type');
}
// Set stopped to false to denote that it's running.
this.stopped = false;
return this; // Chainable.
}
// Stop the loop.
stop() {
this.log('stop');
// Don't need to stop if it's already stopped.
if(this.stopped === true) {
this.log('stop', 'not called because it\'s already stopped');
return this; // Chainable.
}
// A timeout type loop.
if(this.loopType === 'timeout') {
clearInterval(this.intervalID);
this.log('stop', 'stopped the timeout');
}
// An interval type loop.
else {
clearTimeout(this.timeoutID);
this.log('stop', 'stopped the interval');
}
// Clear the interval ID.
this.intervalID = null;
// Set stopped to true to denote that it's not running.
this.stopped = true;
// Re-set the last time it was stopped.
this.stoppedAt = new Date();
return this; // Chainable.
}
// Call the task. Will allow the task to throw.
callTask() {
this.log('callTask');
// The current Date. (In case the logging takes time for some reason.)
let now = new Date();
if(this.lastCalled !== null) {
this.log('callTask', 'last called',
(Date.now() - this.lastCalled)/1000, 'seconds ago');
}
// Set the last time the task was called.
this.lastCalled = now;
// undefined output by default.
let output;
// Ensure the task is a function before calling it.
if(typeof this.task === 'function') {
// Put the output from the task into the output variable for
// returning to the caller.
this.log('callTask', 'calling the task (as a function)');
output = this.task.apply(this.bind, this.args);
}
else {
this.log('callTask', 'the task wasn\'t a function but that\'s OK');
}
// If it's a timeout lookType, we have to re-start it again.
if(this.loopType === 'timeout') {
this.log('callTask', 'stopped since it was a timeout type');
this.stopped = true;
this.start();
}
// Returns the output from the task. Not chainable.
return output;
}
}
// A simplification for multiple TaskLoop instances.
class TaskLoopGroup {
constructor(loops) {
// Setup the logger.
let logger = new Logger('TaskLoopGroup', { enabled: true });
this.log = (sec, ...msg) => logger.log(sec, ...msg);
// The array to hold all of the loops.
this.loops = [];
// If you might have arguments that could be used in addLoop.
if(typeof loops !== 'undefined' && (Array.isArray(loops) ||
typeof loops === 'object')) {
this.log(null, 'adding loop');
this.addLoop(loops);
}
}
// Add a loop by either...
addLoop(opts, interval) {
this.log('addLoop');
// A TaskLoop instance to remember.
let loopInstance;
// passing a TaskLoop instance,
if(opts instanceof TaskLoop) {
this.log('addLoop', 'was an instance of TaskLoop');
loopInstance = opts;
}
// or passing an array of TaskLoop instances, (Or even pass an array of
// options for new TaskLoop instances.)
else if(Array.isArray(opts)) {
this.log('addLoop', 'was an array');
opts.forEach(this.addLoop.bind(this)); // Recurse.
// Returns the length of the loops array.
return this.loops.length;
}
// or pass the options for a new one.
else {
this.log('addLoop', 'creating TaskLoop from options');
loopInstance = new TaskLoop(opts, interval);
}
// Push the TaskLoop instance and then return the length of the loops
// array.
return this.loops.push(loopInstance);
}
// Start all loops in the group.
startAll() {
this.log('startAll');
// Loop over the loops.
this.loops.forEach(loop => loop.start());
return this; // Chainable.
}
// Stop all loops in the group.
stopAll() {
this.log('stopAll');
// Loop over the loops.
this.loops.forEach(loop => loop.stop());
return this; // Chainable.
}
// Returns an array containing TaskLoop's from the loops array that match
// the keys and values in the query object.
find(query, strictEqual, _method) {
this.log('find');
// Get the keys from the query object.
let keys = Object.keys(query);
// Default is the "filter" method. (Allow for simplification of the
// fineOne method)
_method = _method || 'filter';
// If _method wasn't a method of the loops array.
if(typeof _method !== 'string' ||
typeof this.loops[_method] !== 'function') {
_method = 'filter';
}
this.log('find', 'Using the method', _method, 'and looking for',
keys.length, 'keys');
// Call the method on the loops array.
return this.loops[_method](loop => {
// Loops over the keys.
return keys.map(key => {
// If there isn't a matching property in the TaskLoop options.
if(!loop.opts.hasOwnProperty(key)) {
return false;
}
// Finally test if the TaskLoop's options value of key matches
// that of the query object. It supports strict and unstrict
// checking and doesn't support a "deep" (recursive) loop for
// objects.
return (strictEqual !== true && loop.opts[key] == query[key]) ||
(strictEqual === true && loop.opts[key] === query[key]);
});
});
}
// Returns a single TaskLoop from the loops array that matches the keys and
// values in the query object.
findOne(query, strictEqual) {
this.log('findOne');
return this.find(query, strictEqual, 'find');
}
}
// Export the classes.
module.exports = {
TaskLoop,
TaskLoopGroup
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment