Last active
June 11, 2016 11:04
-
-
Save AlcaDesign/48f1fa4d65adb15cf9342f24ee96cec6 to your computer and use it in GitHub Desktop.
Possible solution for https://www.tmijs.org/forums/index.php?/topic/78-need-some-help/#comment-238
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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