-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package-lock.json | ||
config.json | ||
build/ | ||
node_modules/ |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Remove all files from ./build/ | ||
Get-ChildItem -File -Recurse -Path "./build/" | ` | ||
ForEach-Object { | ||
Remove-Item -Path $_.FullName; | ||
} | ||
|
||
# Compile typescript | ||
tsc | ||
|
||
# Package the app | ||
pkg --out-path ./build ./build/src/index.js |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "tmi-public-npbot", | ||
"version": "2.0.1", | ||
"description": "grabs now playing map from gosumemory and displays it in chat upon request", | ||
"main": "./src/index.ts", | ||
"scripts": { | ||
"run": "ts-node ./src/index.ts", | ||
"build": "powershell.exe -File build.ps1" | ||
}, | ||
"keywords": [ | ||
"tmi.js", | ||
"twitch", | ||
"osu", | ||
"now-playing", | ||
"chat-bot", | ||
"twitch-chat-bot", | ||
"np" | ||
], | ||
"author": "thymuue", | ||
"license": "ISC", | ||
"dependencies": { | ||
"@types/node": "^15.12.2", | ||
"@types/tmi.js": "^1.7.1", | ||
"@types/websocket": "^1.0.2", | ||
"tmi.js": "^1.8.3", | ||
"websocket": "^1.0.34" | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { client as WebSocketClient, connection as WebSocketConnection, IMessage as IWebSocketMessage } from "websocket"; | ||
|
||
export default class GOsuMemoryHook | ||
{ | ||
public npBeatmapId: number = -31; | ||
public die: boolean = false; | ||
private gOsuWebSocket: WebSocketClient; | ||
private port: number; | ||
|
||
constructor(port: number) | ||
{ | ||
console.log(`[${new Date().toLocaleTimeString()}] Initializing gosumemory websocket...`); | ||
this.gOsuWebSocket = new WebSocketClient(); | ||
this.port = port; | ||
|
||
this.gOsuWebSocket.on("connect", (webSocketConnection: WebSocketConnection) => { | ||
console.log(`[${new Date().toLocaleTimeString()}] Successfully connected to gosumemory websocket.`); | ||
webSocketConnection.on("message", (webSocketMessage: IWebSocketMessage) => this.handleWebSocketMessage(webSocketMessage)); | ||
webSocketConnection.on("error", (error: Error) => this.handleWebSocketError(error)); | ||
}); | ||
this.gOsuWebSocket.on("connectFailed", (error: Error) => this.handleWebSocketError(error)); | ||
|
||
this.gOsuWebSocket.connect(`ws://127.0.0.1:${port}/ws`); | ||
} | ||
|
||
handleWebSocketMessage(webSocketMessage: IWebSocketMessage) | ||
{ | ||
this.npBeatmapId = JSON.parse(webSocketMessage.utf8Data).menu.bm.id; | ||
} | ||
|
||
handleWebSocketError(e: Error) | ||
{ | ||
if(this.die) return; | ||
if(e.message.indexOf("ECONNREFUSED") > -1) | ||
{ | ||
console.error(`[${new Date().toLocaleTimeString()}] ERROR: Couldn't connect to gosumemory websocket are you sure it's running? Retrying in 5 seconds.`); | ||
|
||
setTimeout(() => | ||
{ | ||
this.gOsuWebSocket.connect(`ws://127.0.0.1:${this.port}/ws`); | ||
}, 5000); | ||
} | ||
else if(e.message.indexOf("ECONNRESET") > -1) | ||
{ | ||
this.npBeatmapId = -31; | ||
console.error(`[${new Date().toLocaleTimeString()}] ERROR: Seems like gosumemory websocket stopped working, we'll try to reconnect in 5 seconds.`); | ||
|
||
setTimeout(() => | ||
{ | ||
this.gOsuWebSocket.connect(`ws://127.0.0.1:${this.port}/ws`); | ||
}, 5000); | ||
} | ||
else | ||
{ | ||
console.error(`[${new Date().toLocaleTimeString()}] ERROR: Unrecognized error has occured. You can probably just restart the application and it should be working again.`); | ||
console.error(`${e.name}\n${e.message}\n${e.stack}`); | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default interface INpBotConfiguration | ||
{ | ||
botUsername: string, | ||
botPassword: string, | ||
twitchChannelName: string, | ||
gOsuMemoryPort?: number | ||
npCommand: string | ||
useActionInsteadOfMessage: boolean | ||
npMessage: string | ||
npGOsuMemoryNotReadyMessage?: string | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { ChatUserstate, Client as TmiClient } from "tmi.js"; | ||
import * as readline from "readline"; | ||
import GOsuMemoryHook from "./GOsuMemoryHook"; | ||
import INpBotConfiguration from "./INpBotConfiguration"; | ||
|
||
export default class NpBot | ||
{ | ||
private client: TmiClient; | ||
private gOsuMemoryHook: GOsuMemoryHook; | ||
private npBotConfiguration: INpBotConfiguration; | ||
|
||
constructor(npBotConfiguration: INpBotConfiguration) | ||
{ | ||
console.log(`[${new Date().toLocaleTimeString()}] Initializing Twitch chat bot...`); | ||
this.client = new TmiClient({ | ||
identity: { | ||
username: npBotConfiguration.botUsername, | ||
password: npBotConfiguration.botPassword | ||
} | ||
}); | ||
this.gOsuMemoryHook = new GOsuMemoryHook(npBotConfiguration.gOsuMemoryPort); | ||
|
||
this.npBotConfiguration = npBotConfiguration; | ||
|
||
this.client.on("join", (channel, username, self) => | ||
{ | ||
if(self) console.log(`[${new Date().toLocaleTimeString()}] Successfully joined twitch chat channel ${channel} as ${username}.`); | ||
}); | ||
this.client.on("message", (channel, userstate, message, self) => this.handleMessage(channel, userstate, message, self)); | ||
this.client.on("notice", (channel, msgid, message) => {console.log(message)}); | ||
|
||
this.client.connect().catch((error: string) => this.handleConnectionError(error)).then(() => { | ||
this.client.join(npBotConfiguration.twitchChannelName).catch((error: string) => this.handleConnectionError(error)); | ||
}); | ||
} | ||
|
||
handleMessage(channel: string, userstate: ChatUserstate, message: string, self: boolean) | ||
{ | ||
if(self) return; | ||
|
||
if(message.toLowerCase() === this.npBotConfiguration.npCommand.toLowerCase()) | ||
{ | ||
if(this.gOsuMemoryHook.npBeatmapId == -31) | ||
{ | ||
if(this.npBotConfiguration.npGOsuMemoryNotReadyMessage != null) | ||
this.client.say(channel, this.npBotConfiguration.npGOsuMemoryNotReadyMessage.replace("{{username}}", userstate.username)); | ||
} | ||
else | ||
{ | ||
const npMessage = this.npBotConfiguration.npMessage | ||
.replace("{{username}}", userstate.username) | ||
.replace("{{beatmapLink}}", `https://osu.ppy.sh/b/${this.gOsuMemoryHook.npBeatmapId}`); | ||
if(this.npBotConfiguration.useActionInsteadOfMessage) | ||
this.client.action(channel, npMessage); | ||
else | ||
this.client.say(channel, npMessage); | ||
} | ||
} | ||
} | ||
|
||
handleConnectionError(error: string) | ||
{ | ||
// If GOsuMemoryHook is marked as dieded this should also die | ||
if(this.gOsuMemoryHook.die) return; | ||
if(error.indexOf("Login authentication failed") > -1) | ||
{ | ||
console.error(`[${new Date().toLocaleTimeString()}] Failed to log in, please make sure your botUsername and botPassword in config.json are correct.`); | ||
const readLineInterface = readline.createInterface(process.stdin, process.stdout); | ||
this.gOsuMemoryHook.die = true; | ||
readLineInterface.question("", () => { | ||
process.exit(0); | ||
}); | ||
} | ||
else if(error.indexOf("No response from Twitch.") > -1) | ||
{ | ||
console.error(`[${new Date().toLocaleTimeString()}] Failed to join channel, please make sure your twitchChannelName in config.json is correct.`); | ||
const readLineInterface = readline.createInterface(process.stdin, process.stdout); | ||
this.gOsuMemoryHook.die = true; | ||
readLineInterface.question("", () => { | ||
process.exit(0); | ||
}); | ||
} | ||
else | ||
{ | ||
console.error(`[${new Date().toLocaleTimeString()}] ERROR: Unrecognized error has occured.`); | ||
console.error(error); | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// TODO: Make process.exits(0) just pause the app indefinitely or something because now it just exits the application without giving them a chance to read anything | ||
import { existsSync, readFileSync, writeFileSync } from "fs"; | ||
import * as readline from "readline"; | ||
import INpBotConfiguration from "./INpBotConfiguration"; | ||
import NpBot from "./NpBot"; | ||
|
||
// Load configuration | ||
let npBotConfiguration: INpBotConfiguration; | ||
if(existsSync(`./config.json`)) | ||
{ | ||
npBotConfiguration = JSON.parse(readFileSync(`./config.json`).toString()); | ||
} | ||
else | ||
{ | ||
npBotConfiguration = { | ||
botUsername: "", | ||
botPassword: "", | ||
twitchChannelName: "", | ||
gOsuMemoryPort: 24050, | ||
npCommand: "!np", | ||
useActionInsteadOfMessage: true, | ||
npMessage: "@{{username}}, here u go: {{beatmapLink}}", | ||
npGOsuMemoryNotReadyMessage: "gosumemory died Sadge, please restart me" | ||
} | ||
writeFileSync(`./config.json`, JSON.stringify(npBotConfiguration, null, "\t")); | ||
} | ||
|
||
// Check if every required value is defined | ||
const requiredValues = ["botUsername", "botPassword", "twitchChannelName", "npCommand", "npMessage"] | ||
let exitAfter = false; | ||
for(const requiredValue in requiredValues) | ||
{ | ||
if(npBotConfiguration[requiredValues[requiredValue]] == undefined || npBotConfiguration[requiredValues[requiredValue]] == "") | ||
{ | ||
console.error(`You have to set a value for ${requiredValues[requiredValue]} in config.json`); | ||
exitAfter = true; | ||
} | ||
} | ||
if(exitAfter) | ||
{ | ||
const readLineInterface = readline.createInterface(process.stdin, process.stdout); | ||
readLineInterface.question("", () => { | ||
process.exit(0); | ||
}); | ||
} | ||
else | ||
{ | ||
// Define defaults if optional values are undefined | ||
if(npBotConfiguration.gOsuMemoryPort == undefined) npBotConfiguration.gOsuMemoryPort = 24050; | ||
if(npBotConfiguration.npGOsuMemoryNotReadyMessage == undefined) npBotConfiguration.npGOsuMemoryNotReadyMessage = null; | ||
if(npBotConfiguration.useActionInsteadOfMessage == undefined) npBotConfiguration.useActionInsteadOfMessage = true; | ||
|
||
// Run the bot | ||
const npBot = new NpBot(npBotConfiguration); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"esModuleInterop": true, | ||
"allowSyntheticDefaultImports": true, | ||
"target": "es6", | ||
"noImplicitAny": false, | ||
"moduleResolution": "node", | ||
"sourceMap": true, | ||
"outDir": "build/src/", | ||
"baseUrl": ".", | ||
"paths": { | ||
"*": [ | ||
"node_modules/*", | ||
"src/types/*" | ||
] | ||
} | ||
}, | ||
"include": [ | ||
"src/**/*" | ||
] | ||
} |