@coldino/tmi.js-clientonly-fork
Version:
Javascript library for the Twitch Messaging Interface, forked to remove all node requirements (to simplify client bundling).
163 lines (136 loc) • 5.19 kB
JavaScript
// eslint-disable-next-line no-control-regex
const actionMessageRegex = /^\u0001ACTION ([^\u0001]+)\u0001$/;
const justinFanRegex = /^(justinfan)(\d+$)/;
const unescapeIRCRegex = /\\([sn:r\\])/g;
const escapeIRCRegex = /([ \n;\r\\])/g;
const ircEscapedChars = { s: ' ', n: '', ':': ';', r: '' };
const ircUnescapedChars = { ' ': 's', '\n': 'n', ';': ':', '\r': 'r' };
const _ = module.exports = {
// Return the second value if the first value is undefined..
get: (a, b) => typeof a === 'undefined' ? b : a,
// Indirectly use hasOwnProperty
hasOwn: (obj, key) => ({}).hasOwnProperty.call(obj, key),
// Race a promise against a delay..
promiseDelay: time => new Promise(resolve => setTimeout(resolve, time)),
// Value is a finite number..
isFinite: int => isFinite(int) && !isNaN(parseFloat(int)),
// Parse string to number. Returns NaN if string can't be parsed to number..
toNumber: (num, precision) => {
if(num === null) {
return 0;
}
const factor = Math.pow(10, _.isFinite(precision) ? precision : 0);
return Math.round(num * factor) / factor;
},
// Value is an integer..
isInteger: int => !isNaN(_.toNumber(int, 0)),
// Merge two arrays..
union: (a, b) => [ ...new Set([ ...a, ...b ]) ],
// Value is a regex..
isRegex: str => /[|\\^$*+?:#]/.test(str),
// Value is a valid url..
isURL: str => new RegExp('^(?:(?:https?|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))\\.?)(?::\\d{2,5})?(?:[/?#]\\S*)?$', 'i').test(str),
// Return a random justinfan username..
justinfan: () => `justinfan${Math.floor((Math.random() * 80000) + 1000)}`,
// Username is a justinfan username..
isJustinfan: username => justinFanRegex.test(username),
// Return a valid channel name..
channel: str => {
const channel = (str ? str : '').toLowerCase();
return channel[0] === '#' ? channel : '#' + channel;
},
// Return a valid username..
username: str => {
const username = (str ? str : '').toLowerCase();
return username[0] === '#' ? username.slice(1) : username;
},
// Return a valid token..
token: str => str ? str.toLowerCase().replace('oauth:', '') : '',
// Return a valid password..
password: str => {
const token = _.token(str);
return token ? `oauth:${token}` : '';
},
actionMessage: msg => msg.match(actionMessageRegex),
// Replace all occurences of a string using an object..
replaceAll: (str, obj) => {
if(str === null || typeof str === 'undefined') {
return null;
}
for (const x in obj) {
str = str.replace(new RegExp(x, 'g'), obj[x]);
}
return str;
},
unescapeHtml: safe =>
safe.replace(/\\&\\;/g, '&')
.replace(/\\<\\;/g, '<')
.replace(/\\>\\;/g, '>')
.replace(/\\"\\;/g, '"')
.replace(/\\'\\;/g, '\''),
// Escaping values:
// http://ircv3.net/specs/core/message-tags-3.2.html#escaping-values
unescapeIRC: msg => {
if(!msg || typeof msg !== 'string' || !msg.includes('\\')) {
return msg;
}
return msg.replace(
unescapeIRCRegex,
(m, p) => p in ircEscapedChars ? ircEscapedChars[p] : p
);
},
escapeIRC: msg => {
if(!msg || typeof msg !== 'string') {
return msg;
}
return msg.replace(
escapeIRCRegex,
(m, p) => p in ircUnescapedChars ? `\\${ircUnescapedChars[p]}` : p
);
},
// Add word to a string..
addWord: (line, word) => line.length ? line + ' ' + word : line + word,
// Split a line but try not to cut a word in half..
splitLine: (input, length) => {
let lastSpace = input.substring(0, length).lastIndexOf(' ');
// No spaces found, split at the very end to avoid a loop..
if(lastSpace === -1) {
lastSpace = length - 1;
}
return [ input.substring(0, lastSpace), input.substring(lastSpace + 1) ];
},
// Extract a number from a string..
extractNumber: str => {
const parts = str.split(' ');
for (let i = 0; i < parts.length; i++) {
if(_.isInteger(parts[i])) {
return ~~parts[i];
}
}
return 0;
},
// Format the date..
formatDate: date => {
let hours = date.getHours();
let mins = date.getMinutes();
hours = (hours < 10 ? '0' : '') + hours;
mins = (mins < 10 ? '0' : '') + mins;
return `${hours}:${mins}`;
},
// Inherit the prototype methods from one constructor into another..
inherits: (ctor, superCtor) => {
ctor.super_ = superCtor;
const TempCtor = function () {};
TempCtor.prototype = superCtor.prototype;
ctor.prototype = new TempCtor();
ctor.prototype.constructor = ctor;
},
// Return whether inside a Node application or not..
isNode: () => {
try {
return typeof process === 'object' &&
Object.prototype.toString.call(process) === '[object process]';
} catch(e) {}
return false;
}
};