UNPKG

twitch-kit

Version:

Twitch Bot Package with amazing features like Title Update.

376 lines (307 loc) 8.92 kB
'use strict' const tls = require('tls'); const assert = require('assert'); const utils = require('./utils'); const api = require('./api'); const EventEmitter = require('events').EventEmitter; const WebSocket = require('websocket').w3cwebsocket; let ws; const TwitchBot = class TwitchBot extends EventEmitter { constructor({ username, oauth, channels = [], port = 443 }) { super(); try { assert(username); assert(oauth); } catch (err) { throw new Error('invalid arguments'); } this.username = username; this.oauth = oauth; this.port = port; this.channels = channels; this.irc = new tls.TLSSocket(); //this.pubsub = pubsub; //this.topics = topics; this._connect(); //if(this.pubsub) { // this.connectSub(); //} } async _connect() { this.irc.connect({ host: 'irc.chat.twitch.tv', port: this.port }); this.irc.setEncoding('utf-8'); this.irc.once('connect', () => { this.irc.on('error', err => this.emit('error', err)); this.listen(); this.write("PASS " + this.oauth); this.write("NICK " + this.username); this.channels.forEach(channel => this.join(channel)); this.write("CAP REQ :twitch.tv/tags"); this.write("CAP REQ :twitch.tv/membership"); this.write("CAP REQ :twitch.tv/commands"); this.emit('connected'); }); } async write(message) { this.irc.write(message + "\r\n"); } async join(channel) { channel = utils.formatCHANNEL(channel); this.write("JOIN " + channel); } async check(text) { let res = await utils.checkErrors(text); switch (res) { case "LOGIN": this.irc.emit('error', { message: "Login authentication failed" }); break; case "AUTH": this.irc.emit('error', { message: "Improperly formatted auth" }); break; case "SPAM": this.irc.emit('error', { message: "Your message was not sent because you are sending messages too quickly" }); break; case "OK": break; } } async leave(channel) { if (!channel && this.channels.length > 0) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.write("PART " + channel); } async listen() { this.irc.on('data', data => { this.emit('ircdata', data); this.check(data); /** HeartBeat */ if (data.includes('PING :tmi.twitch.tv')) { this.irc.write('PONG :tmi.twitch.tv\r\n'); } if (data.includes('PRIVMSG')) { const chatter = utils.formatPRIVMSG(data); this.emit('message', chatter); } if (data.includes('CLEARCHAT')) { const event = utils.formatCLEARCHAT(data); if (event.type === 'timeout') this.emit('timeout', event); if (event.type === 'ban') this.emit('ban', event); } if (data.includes('USERNOTICE')) { const event = utils.formatUSERNOTICE(data); if (['sub', 'resub', 'subgift'].includes(event.msg_id)) { this.emit('subscription', event); } } if (data.includes('CLEARMSG')) { const event = utils.formatCLEARMSG(data); this.emit('messagedeletion'); } if (data.includes(`@${this.username}.tmi.twitch.tv JOIN`)) { const channel = utils.formatJOIN(data).split(':')[0]; if (channel) { if (!this.channels.includes(channel)) { this.channels.push(channel); } this.emit('join', channel); } } if (data.includes(`@${this.username}.tmi.twitch.tv PART`)) { const channel = utils.formatPART(data).split(':')[0]; if (channel) { if (this.channels.includes(channel)) { this.channels.pop(channel); } this.emit('part', channel); } } }); } async callback(callback, obj) { if (callback) { obj.ts = new Date(); callback(obj); } } async say(message, channel, callback) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); if (message.length >= 500) { this.callback(callback, { sent: false, message: 'Message to long (>=500)' }) } else { this.write('PRIVMSG ' + channel + ' :' + message); } } async me(message, channel, callback) { this.say(`/me ${message}`, channel, callback); } async timeout(username, channel, duration = 600, reason = '') { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/timeout ${username} ${duration} ${reason}`, channel); } async ban(username, channel, reason = '') { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/ban ${username} ${reason}`, channel); } async emoteChat(state, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); if (state) this.say(`/emoteonly`, channel); else this.say(`/emoteonlyoff`, channel); } async followerChat(state, channel, duration = null) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); if (state) this.say(`/followers ${duration}`); else this.say(`/followersoff`, channel); } async subscriberChat(state, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); if (state) this.say(`/subscribers`); else this.say(`/subscribersoff`, channel); } async uniqueChat(state, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); if (state) this.say(`/uniquechat`, channel); else this.say(`/uniquechatoff`, channel); } async slowChat(state, channel, delay = 10) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); if (state) this.say(`/slow ${delay}`, channel); else this.say(`/slowoff`, channel); } async clearChat(channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/clear`, channel); } async setMod(username, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/mod ${username}`, channel); } async removeMod(username, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/unmod ${username}`, channel); } async setVip(username, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/vip ${username}`, channel); } async removeVip(username, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/unvip ${username}`, channel); } async setColor(color) { channel = utils.formatCHANNEL(this.channels[0]); this.say(`/color ${color}`, channel); } async removeBan(username, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/unban ${username}`, channel); } async removeTimeout(username, channel) { if (!channel) { channel = this.channels[0]; } channel = utils.formatCHANNEL(channel); this.say(`/untimeout ${username}`, channel); } async updateTitle(title, channel) { let id = await api.getUserByName(channel, this.oauth)['_id']; await api.updateTitle(id, title, this.oauth); } async updateGame(game, channel) { let id = await api.getUserByName(channel, this.oauth)['_id']; await api.updateGame(id, game, this.oauth); } async getChannel(channel) { let id = await api.getUserByName(channel)['_id']; return await api.getChannel(id, this.oauth); } async close() { this.irc.destroy(); this.emit('close'); } async _nonce(length) { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < length; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } async _listenPubSub(topics, channel) { channel = api.getUserByName(channel, this.oauth); let x = []; topics.forEach((topic) => { x.push(topic + "." + channel['_id']); }) let message = { type: 'LISTEN', nonce: this._nonce(15), data: { topics: x, auth_token: this.oauth } }; if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(message)); } } module.exports = TwitchBot;