mixer-client-node
Version:
A node client for connecting to mixer and the mixer services
545 lines (544 loc) • 20.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const HelperFunctions_1 = require("../Util/HelperFunctions");
let WebSocket;
if (typeof window === 'undefined')
WebSocket = require('ws');
else
WebSocket = window.WebSocket;
class ChatService extends events_1.EventEmitter {
constructor(client) {
super();
this.autoReconnect = new Map();
this.socket = new Map();
this.listener = new Map();
this.currentId = 0;
this.client = client;
}
/*
* Join a chat
*/
join(userid, channelid, autoReconnect = false) {
return new Promise(async (resolve, deny) => {
if (!this.client.user || userid !== this.client.user.userid) {
let id;
if (this.client.user)
id = this.client.user.channelid;
this.client.user = {
channelid: id || channelid,
userid,
};
}
if (this.socket.get(channelid))
this.close(channelid, false);
try {
resolve(await this.connectTheChat(channelid, autoReconnect));
}
catch (error) {
if (this.listenerCount('error') === 0)
deny(error);
else
this.emit('error', error, channelid);
}
});
}
joinWithAuthKey({ userid, channelid, authKey, endpoints }) {
if (!this.client.user || userid !== this.client.user.userid) {
let id;
if (this.client.user)
id = this.client.user.channelid;
this.client.user = {
channelid: id || channelid,
userid,
};
}
if (!userid)
userid = this.client.user.userid;
if (!channelid)
channelid = this.client.user.channelid;
if (!authKey) {
return this.emit('error', "No AuthKey", channelid);
}
else if (!endpoints || !endpoints.length) {
return this.emit('error', "No Enpoints", channelid);
}
if (this.socket.get(channelid))
this.close(channelid, false);
this.autoReconnect.set(channelid, false);
this.socket.set(channelid, new WebSocket(endpoints[0]));
this.hookEventListeners(channelid, authKey);
}
connectTheChat(channelid, autoReconnect) {
return new Promise(async (resolve, deny) => {
const chatRequest = {
auth: true,
method: 'GET',
uri: 'https://mixer.com/api/v1/chats/' + channelid,
};
try {
const response = await this.client.request(chatRequest);
if (!response.authkey) {
deny({
code: 401,
error: 'Not Authenticated',
id: 1,
message: 'You must be authenticated to connect to a chat!',
});
}
else {
this.autoReconnect.set(channelid, autoReconnect);
this.socket.set(channelid, new WebSocket(response.endpoints[0]));
this.hookEventListeners(channelid, response.authkey);
resolve(this.socket.get(channelid));
}
}
catch (error) {
deny(error);
}
});
}
ping(channelid) {
if (this.timeout)
clearTimeout(this.timeout);
if (this.ensurePingTimeout)
clearTimeout(this.ensurePingTimeout);
this.timeout = setTimeout(() => {
if (this.socket.get(channelid).readyState !== 1)
this.emit('error', { socket: 'Closed', from: 'Ping' });
else {
if (this.currentId > 100000000)
this.currentId = 0;
this.pingId = ++this.currentId;
this.sendPacket('ping', [], channelid, this.pingId);
this.ensurePingTimeout = setTimeout(() => {
this.socket.get(channelid).close();
}, 1500); // ensure ping recieved in 1.5s
}
}, 1000 * 5); // send next ping in 5s from last revieved
}
/*
* Setup the event listeners for the sockets
*/
hookEventListeners(channelid, authkey) {
this.listener.set(channelid, true);
this.socket.get(channelid).addEventListener('open', () => {
this.sendPacket('auth', [channelid, this.client.user.userid, authkey], channelid, 0);
this.ping(channelid);
});
this.socket.get(channelid).addEventListener('message', (data) => {
if (!this.listener.get(channelid) || this.eventNames().length === 0)
return;
const response = HelperFunctions_1.toJSON(data.data);
if (response.type === 'reply') {
if (response.data &&
response.data.hasOwnProperty('authenticated')) {
if (response.data.authenticated === true &&
response.id === 0) {
this.emit('joined', {
connectedTo: channelid,
userConnected: this.client.user.userid,
});
}
else {
this.close(channelid, false);
this.emit('error', {
code: 401,
error: 'Not Authenticated',
id: 2,
message: 'You must be authenticated to connect to a chat!',
}, channelid);
}
}
else {
if (response.id === this.pingId)
this.ping(channelid);
else
this.emit(response.type, response.error, response.data, channelid);
}
}
else {
if (response.event === 'ChatMessage') {
const messageMeta = response.data.message.meta;
const messageResponse = response.data.message.message;
const hasLink = messageResponse.filter((part) => part.type === 'link')
.length > 0;
const isWhisper = messageMeta.whisper ? true : false;
const text = messageResponse
.map((part) => part.text)
.join('')
.replace(/\r?\n|\r/g, '')
.trim();
const tags = messageResponse
.filter((part) => part.type === 'tag')
.map((tag) => tag.username);
const message = { hasLink, text, tags, isWhisper };
const isCommand = text.startsWith('!');
const trigger = isCommand ? text.split(' ')[0] : undefined;
const args = isCommand
? text.split(' ').slice(1)
: undefined;
const command = isCommand ? { args, trigger } : undefined;
const isSkill = messageMeta.is_skill ? true : false;
const skillType = isSkill
? messageMeta.skill.currency
: undefined;
const skillCost = isSkill
? messageMeta.skill.cost
: undefined;
const skillImage = isSkill
? messageMeta.skill.icon_url
: undefined;
const skillName = isSkill
? messageMeta.skill.skill_name
: undefined;
const skillId = isSkill
? messageMeta.skill.skill_id
: undefined;
const skillMessage = isSkill
? text.replace(skillName, '')
: undefined;
const skill = isSkill
? {
cost: skillCost,
id: skillId,
image: skillImage,
message: skillMessage,
name: skillName,
type: skillType,
}
: undefined;
const addProperties = { command, message, skill };
this.emit(response.event, HelperFunctions_1.mergeDeep(response.data, addProperties), channelid);
}
else
this.emit(response.event, response.data, channelid);
}
});
this.socket.get(channelid).addEventListener('error', (error) => {
if (!this.listener.get(channelid) ||
this.listenerCount('error') === 0)
return;
this.emit('error', error, channelid);
});
this.socket.get(channelid).addEventListener('close', (data) => {
if (this.listener.get(channelid) &&
!this.autoReconnect.get(channelid))
this.emit('closed', channelid, {
code: data.code,
reason: data.reason,
});
this.close(channelid, this.autoReconnect.get(channelid));
});
}
unlisten(channelid) {
const id = this.socket.size === 1 ? this.socket.keys().next().value : channelid;
if (id && this.chatSocket(id))
this.listener.set(id, false);
}
listen(channelid) {
const id = this.socket.size === 1 ? this.socket.keys().next().value : channelid;
if (id && this.chatSocket(id))
this.listener.set(id, true);
}
/*
* Get a list of the chats you are connected to
*/
get connectedChats() {
return [...this.socket.keys()];
}
/*
* Get a specific chat socket
*/
chatSocket(id) {
return this.socket.get(id);
}
/*
* Methods to tell the chat server what to do
*/
/*
* Send the server a packet of info
*/
sendPacket(method, args, channelid, id = 0) {
const packet = {
arguments: args,
id,
method,
type: 'method',
};
if (this.socket.get(channelid)) {
if (this.socket.get(channelid).readyState === 1)
this.socket.get(channelid).send(JSON.stringify(packet));
else {
this.emit('warning', {
channelid,
code: 1000,
id: 3,
packet: {
args,
channelid,
id,
method,
},
reason: 'Socket is Closed',
warning: "Can't Send Packet",
});
}
}
else {
this.emit('warning', {
channelid,
code: 1000,
id: 1,
packet: {
args,
channelid,
id,
method,
},
reason: 'No Socket Found',
warning: "Can't Send Packet",
});
}
}
/*
* Send a chat message to a channel
*/
sendMessage(message, channelid = this.socket.size === 1 ? this.socket.keys().next().value : 0) {
if (this.socket.get(channelid)) {
if (message && message.length > 360) {
const getPart = () => {
const part = message
.substr(0, message.lastIndexOf(' ', 360))
.trim();
this.sendMessage(part, channelid);
message = message.replace(part, '').trim();
setTimeout(() => {
if (message.length <= 360) {
if (message.trim().length !== 0) {
this.sendMessage(message, channelid);
}
}
else {
getPart();
}
}, 100);
};
getPart();
}
else {
if (message) {
this.sendPacket('msg', [message], channelid, ++this.currentId);
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'You must specify the message to send',
warning: "Can't Send Packet",
});
}
}
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'No ChannelID Specified, you MUST specify this when connected to more than one chat',
warning: "Can't Send Packet",
});
}
}
/*
* Send a whisper message to a user in a channel
*/
sendWhisper(message, sendToUser, channelid = this.socket.size === 1 ? this.socket.keys().next().value : 0) {
if (this.socket.get(channelid)) {
if (message && message.length > 360) {
const getPart = () => {
const part = message.substr(0, message.lastIndexOf(' ', 360));
this.sendWhisper(part, sendToUser, channelid);
message = message.replace(part, '');
setTimeout(() => {
if (message.length <= 360) {
if (message.trim().length !== 0) {
this.sendWhisper(message, sendToUser, channelid);
}
}
else {
getPart();
}
}, 100);
};
getPart();
}
else {
if (sendToUser && message) {
this.sendPacket('whisper', [sendToUser, message], channelid, ++this.currentId);
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'You must specify the message and user to send the message to',
warning: "Can't Send Packet",
});
}
}
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'No ChannelID Specified, you MUST specify this when connected to more than one chat',
warning: "Can't Send Packet",
});
}
}
/*
* Delete a message
*/
deleteMessage(messageID, channelid = this.socket.size === 1 ? this.socket.keys().next().value : 0) {
if (this.socket.get(channelid)) {
if (messageID) {
this.sendPacket('deleteMessage', [messageID], channelid, ++this.currentId);
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'You must specify the id of the message to delete',
warning: "Can't Send Packet",
});
}
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'No ChannelID Specified, you MUST specify this when connected to more than one chat',
warning: "Can't Send Packet",
});
}
}
/*
* Restore a message
*/
restoreMessage(messageID, channelid = this.socket.size === 1 ? this.socket.keys().next().value : 0) {
if (this.socket.get(channelid)) {
if (messageID) {
this.sendPacket('restoreMessage', [messageID], channelid, ++this.currentId);
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'You must specify the id of the message to restore',
warning: "Can't Send Packet",
});
}
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'No ChannelID Specified, you MUST specify this when connected to more than one chat',
warning: "Can't Send Packet",
});
}
}
/*
* Clear chat
*/
clearChat(channelid = this.socket.size === 1 ? this.socket.keys().next().value : 0) {
if (this.socket.get(channelid)) {
this.sendPacket('clearMessages', [], channelid, ++this.currentId);
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'No ChannelID Specified, you MUST specify this when connected to more than one chat',
warning: "Can't Send Packet",
});
}
}
/*
* Timeout a user in a channel
*/
timeoutUser(username, time, channelid = this.socket.size === 1 ? this.socket.keys().next().value : 0) {
if (this.socket.get(channelid)) {
if (username && time) {
this.sendPacket('timeout', [username, time], channelid, ++this.currentId);
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'You must specify the user and length of the timeout',
warning: "Can't Send Packet",
});
}
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'No ChannelID Specified, you MUST specify this when connected to more than one chat',
warning: "Can't Send Packet",
});
}
}
/*
* Purge a user in a channel
*/
purgeUser(username, channelid = this.socket.size === 1 ? this.socket.keys().next().value : 0) {
if (this.socket.get(channelid)) {
if (username) {
this.sendPacket('purge', [username], channelid, ++this.currentId);
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'You must specify the user to purge',
warning: "Can't Send Packet",
});
}
}
else {
this.emit('warning', {
code: 1000,
id: 2,
reason: 'No ChannelID Specified, you MUST specify this when connected to more than one chat',
warning: "Can't Send Packet",
});
}
}
/*
* Close the connection to a chat
*/
close(channelid = this.socket.size === 1
? this.socket.keys().next().value
: 0, rejoin = false) {
if (this.socket.get(channelid)) {
const reconnectSetting = this.autoReconnect.get(channelid);
this.listener.set(channelid, false);
this.socket.get(channelid).close();
this.autoReconnect.delete(channelid);
this.listener.delete(channelid);
this.socket.delete(channelid);
if (rejoin)
this.join(this.client.user.userid, channelid, reconnectSetting);
}
else {
this.emit('warning', {
code: 1001,
id: 1,
reason: 'You MUST provide a valid channelid to close connection to',
warning: 'ChannelID to Close to Not Specified or Found',
});
}
}
}
exports.default = ChatService;