UNPKG

twitch2ma

Version:

Twitch chat bot that runs commands on the MA GrandMA2 using Telnet.

379 lines (253 loc) 14.6 kB
import {TelnetError, ChannelError} from "../src/lib/Twitch2Ma"; import Twitch2Ma from "../src/lib/Twitch2Ma"; import {Config} from "../src/lib/Config"; import TwitchPrivateMessage from "twitch-chat-client/lib/StandardCommands/TwitchPrivateMessage"; import TwitchChatClient from "twitch-chat-client"; import Fs = require("fs"); import _ = require("lodash"); // Workaround, see https://github.com/evanw/node-source-map-support/issues/279 jest.mock("source-map-support"); //// jest.mock("twitch-chat-client"); jest.mock("telnet-client", require("./mocks/telnet-client")); let config: Config; let twitch2Ma: Twitch2Ma; beforeEach(() => { config = loadConfig(); twitch2Ma = getTwitch2MaInstanceAndEnableLogin(); }); afterEach(() => { twitch2Ma.stop(); jest.clearAllMocks(); }); test("Connection chain", async () => { let spyOnTelnetLogin = jest.spyOn(twitch2Ma, "telnetLogin"); let spyOnInitTwitch = jest.spyOn(twitch2Ma, "initTwitch"); let spyOnOnError = jest.spyOn(twitch2Ma, "onError"); let spyOnOnTelnetConnected = jest.fn(); let spyOnOnTwitchConnected = jest.fn(); twitch2Ma.onTelnetConnected(spyOnOnTelnetConnected); twitch2Ma.onTwitchConnected(spyOnOnTwitchConnected); await expect(twitch2Ma.start()).resolves.toBe(false); expect(twitch2Ma["telnet"].connect).toBeCalled(); expect(spyOnTelnetLogin).toBeCalled(); expect(spyOnInitTwitch).toBeCalled(); expect(twitch2Ma["chatClient"].join).toBeCalled(); expect(spyOnOnTelnetConnected).toBeCalledWith(config.ma.host, config.ma.user); expect(spyOnOnTwitchConnected).toBeCalledWith(config.twitch.channel); expect(spyOnOnError).not.toBeCalled(); }); test("Telnet connection failed", async () => { jest.spyOn(twitch2Ma["telnet"], "connect").mockRejectedValueOnce("Fooled!"); let spyOnOnTelnetConnected = jest.fn(); let spyOnOnTwitchConnected = jest.fn(); twitch2Ma.onTelnetConnected(spyOnOnTelnetConnected); twitch2Ma.onTwitchConnected(spyOnOnTwitchConnected); await expect(twitch2Ma.start()).rejects.toThrow(new TelnetError("Could not connect to desk! Check Telnet enabled, " + "MA IP address and firewall settings if using onPC!")); expect(spyOnOnTelnetConnected).not.toBeCalled(); expect(spyOnOnTwitchConnected).not.toBeCalled(); }); test("Telnet login failed", async () => { await twitch2Ma.stop(); twitch2Ma = new Twitch2Ma(config); let spyOnOnTelnetConnected = jest.fn(); let spyOnOnTwitchConnected = jest.fn(); twitch2Ma.onTelnetConnected(spyOnOnTelnetConnected); twitch2Ma.onTwitchConnected(spyOnOnTwitchConnected); await expect(twitch2Ma.start()).rejects .toThrow(new TelnetError(`Could not log into the desk as user ${config.ma.user}! Check password!`)); expect(spyOnOnTelnetConnected).not.toBeCalled(); expect(spyOnOnTwitchConnected).not.toBeCalled(); }); test("Twitch connection failed", async () => { let spyOnOnTelnetConnected = jest.fn(); let spyOnOnTwitchConnected = jest.fn(); twitch2Ma.onTelnetConnected(spyOnOnTelnetConnected); twitch2Ma.onTwitchConnected(spyOnOnTwitchConnected); jest.spyOn(TwitchChatClient, "forTwitchClient") .mockImplementationOnce(() => { throw Error("Not this time!"); }); await expect(twitch2Ma.start()).rejects.toMatchObject(new Error("Not this time!")); expect(spyOnOnTelnetConnected).toBeCalledWith(config.ma.host, config.ma.user); expect(spyOnOnTwitchConnected).not.toBeCalled(); }); test("Twitch channel join failed", async () => { // @ts-ignore TwitchChatClient.joinImplementation.mockImplementationOnce(() => { return () => { return new Promise((resolve, reject) => reject()); } }); let errorHandler = jest.fn(); let spyOnOnTelnetConnected = jest.fn(); let spyOnOnTwitchConnected = jest.fn(); let spyOnError = jest.fn(); twitch2Ma.onTelnetConnected(spyOnOnTelnetConnected); twitch2Ma.onTwitchConnected(spyOnOnTwitchConnected); twitch2Ma.onError(spyOnError); await expect(twitch2Ma.start()).rejects.toThrow(new ChannelError()); expect(twitch2Ma["chatClient"].join).toBeCalled(); expect(spyOnOnTelnetConnected).toBeCalledWith(config.ma.host, config.ma.user); expect(spyOnOnTwitchConnected).not.toBeCalled(); expect(spyOnError).not.toBeCalled(); }) test("Message handler set", async () => { await twitch2Ma.start(); let spyOnMessageHandler = jest.spyOn(twitch2Ma, "handleMessage"); await sendMessageToBot(twitch2Ma, "#mychannel", "myUser", "myMessage", null); expect(spyOnMessageHandler).toBeCalled(); }); test("Send help", async () => { let helpExecutedHandler = jest.fn(); twitch2Ma.onHelpExecuted(helpExecutedHandler); await twitch2Ma.start(); let spyOnTwitchSay = jest.spyOn(twitch2Ma["chatClient"], "say"); await sendMessageToBotAndExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Alice", "!lights", null, `Available commands are: ${config.availableCommands}. Type !lights !command for help.`); expect(helpExecutedHandler).toBeCalledWith("#doesNotMatter", "Alice"); helpExecutedHandler.mockReset(); await sendMessageToBotAndExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Bob", "!lights !doesNotExist", null, "Command !doesNotExist does not exist! Type !lights for a list of available commands."); expect(helpExecutedHandler).not.toBeCalled(); await sendMessageToBotAndExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Celine", "!lights !red", null, "Help for !red: Sets the lights to red"); expect(helpExecutedHandler).toBeCalledWith("#doesNotMatter", "Celine", "red"); await sendMessageToBotAndExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Celine", "!lights blue", null, "Help for !blue: Sets the lights to blue"); expect(helpExecutedHandler).toBeCalledWith("#doesNotMatter", "Celine", "blue"); await sendMessageToBotAndExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Mary", "!lights !yellow", null, "No help for !yellow available!"); expect(helpExecutedHandler).toBeCalledWith("#doesNotMatter", "Mary", "yellow"); await sendMessageToBotAndExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Noely", "!lights !gobo", null, "Help for !gobo: Gobo commands. Available parameters: niceGobo, evenNicerGobo"); expect(helpExecutedHandler).toBeCalledWith("#doesNotMatter", "Noely", "gobo"); }); test("Send help w/o commands", async () => { let helpExecutedHandler = jest.fn(); await twitch2Ma.stop(); twitch2Ma = getTwitch2MaInstanceAndEnableLogin(loadConfig({commands: []})); twitch2Ma.onHelpExecuted(helpExecutedHandler); await twitch2Ma.start(); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!lights", null, "There are no commands available."); expect(helpExecutedHandler).toBeCalledWith("#doesNotMatter", "Alice"); }); test("Command successful", async () => { let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"}); let commandExecutedHandler = jest.fn(); twitch2Ma.onCommandExecuted(commandExecutedHandler); await twitch2Ma.start(); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!red", aliceRawMessage, "@Alice set the lights to red!"); expect(twitch2Ma["telnet"].send).toBeCalledWith("Macro 1"); expect(commandExecutedHandler).toBeCalledWith("#doesNotMatter", "Alice", "red", undefined, "Macro 1"); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!red", aliceRawMessage, "@Alice, please wait \\d{1,2} seconds and try again!"); }); test("Text only command successful", async () => { let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"}); let commandExecutedHandler = jest.fn(); twitch2Ma.onCommandExecuted(commandExecutedHandler); await twitch2Ma.start(); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!textOnly", aliceRawMessage, "Heyyy @Alice!"); expect(twitch2Ma["telnet"].send).not.toBeCalled(); expect(commandExecutedHandler).toBeCalledWith("#doesNotMatter", "Alice", "textOnly", undefined, undefined); }); test("Parameter successful", async () => { let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"}); let commandExecutedHandler = jest.fn(); twitch2Ma.onCommandExecuted(commandExecutedHandler); await twitch2Ma.start(); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!gobo evenNicerGobo", aliceRawMessage, "@Alice wishes the 'evenNicerGobo'!"); expect(twitch2Ma["telnet"].send).toBeCalledWith("Macro 5"); expect(commandExecutedHandler).toBeCalledWith("#doesNotMatter", "Alice", "gobo", "evenNicerGobo", "Macro 5"); }); test("Parameter does not exist", async () => { let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"}); let commandExecutedHandler = jest.fn(); twitch2Ma.onCommandExecuted(commandExecutedHandler); await twitch2Ma.start(); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!gobo doesNotExist", aliceRawMessage, "Parameter doesNotExist does not exist! Type !lights !gobo for help!"); expect(twitch2Ma["telnet"].send).not.toBeCalled(); }); test("Telnet command failed", async () => { let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"}); let commandExecutedHandler = jest.fn(); let errorHandler = jest.fn(); twitch2Ma.onCommandExecuted(commandExecutedHandler); twitch2Ma.onError(errorHandler); jest.spyOn(twitch2Ma["telnet"], "send").mockRejectedValueOnce(new Error()); await twitch2Ma.start(); await sendMessageToBotAndDontExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!red", aliceRawMessage); expect(twitch2Ma["telnet"].send).toBeCalledWith("Macro 1"); expect(errorHandler).toBeCalledWith(new TelnetError("Sending telnet command failed! Is MA still running?")); expect(commandExecutedHandler).not.toBeCalled(); }); test("Command permissions denied", async () => { let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, null, {nick: "Alice"}); let permissionDeniedHandler = jest.fn(); twitch2Ma.onPermissionDenied(permissionDeniedHandler); await twitch2Ma.start(); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!red", aliceRawMessage, "@Alice set the lights to red!"); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!red", aliceRawMessage, "@Alice, please wait \\d{1,2} seconds and try again!"); return expect(permissionDeniedHandler).toBeCalledWith("#doesNotMatter", "Alice", "!red", "cooldown"); }); test("Command permissions denied but godMode enabled", async () => { let aliceRawMessage = new TwitchPrivateMessage("doesNotMatter", null, new Map([["badges", "moderator"]]), {nick: "Alice"}); let godModeHandler = jest.fn(); twitch2Ma.onGodMode(godModeHandler); await twitch2Ma.start(); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!red", aliceRawMessage, "@Alice set the lights to red!"); await sendMessageToBotAndExpectAnswer(twitch2Ma, jest.spyOn(twitch2Ma["chatClient"], "say"), "#doesNotMatter", "Alice", "!red", aliceRawMessage, "@Alice set the lights to red!"); return expect(godModeHandler).toBeCalledWith("#doesNotMatter", "Alice", "user is moderator"); }) test("Message not involving the bot", async () => { await twitch2Ma.start(); let spyOnTwitchSay = jest.spyOn(twitch2Ma["chatClient"], "say"); await sendMessageToBotAndDontExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Alice", "Hello world!", null); await sendMessageToBotAndDontExpectAnswer(twitch2Ma, spyOnTwitchSay, "#doesNotMatter", "Alice", "!command", null); }); async function sendMessageToBotAndExpectAnswer(twitch2Ma: Twitch2Ma, spyOnTwitchSay: jest.SpyInstance, channel: string, user: string, message: string, rawMessage: TwitchPrivateMessage, answer: string) { await sendMessageToBot(twitch2Ma, channel, user, message, rawMessage); expect(spyOnTwitchSay).toBeCalledWith(channel, expect.stringMatching(answer)); } async function sendMessageToBotAndDontExpectAnswer(twitch2Ma: Twitch2Ma, spyOnTwitchSay: jest.SpyInstance, channel: string, user: string, message: string, rawMessage: TwitchPrivateMessage) { await sendMessageToBot(twitch2Ma, channel, user, message, rawMessage); expect(spyOnTwitchSay).not.toBeCalled(); } async function sendMessageToBot(twitch2Ma: Twitch2Ma, channel: string, user: string, message: string, rawMessage: TwitchPrivateMessage) { // @ts-ignore await twitch2Ma["chatClient"].onPrivmsgHandler(channel, user, message, rawMessage); } function getTwitch2MaInstanceAndEnableLogin(newConfig?: Config): Twitch2Ma { let twitch2Ma = new Twitch2Ma(newConfig || config); jest.spyOn(twitch2Ma["telnet"], "exec") .mockResolvedValueOnce(`Logged in as User '${config.ma.user}'`); return twitch2Ma; } function loadConfig(overrideConfigValues?: any): Config { return new Config(_.assign({}, JSON.parse(Fs.readFileSync("config.json.sample", {encoding: "utf-8"})), overrideConfigValues)); }