-
Notifications
You must be signed in to change notification settings - Fork 1
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
2 changed files
with
367 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,339 @@ | ||
const CLIENT_ID = 'dl6k4oj0aiuyymn19opsclt6xyykbc'; | ||
|
||
var BOT_USER_ID; // This is the User ID of the chat bot | ||
var CHAT_CHANNEL_USER_ID; // This is the User ID of the channel that the bot will join and listen to chat messages of | ||
|
||
function verifyUrl() { | ||
let hash; | ||
try { | ||
hash = window.location.hash.split('access_token=')[1].split('&')[0]; | ||
} catch (error) { | ||
hash = "" | ||
} | ||
return hash; | ||
} | ||
|
||
const OAUTH_TOKEN = verifyUrl(); | ||
|
||
document.getElementById("token").value = OAUTH_TOKEN; | ||
console.log("OAuth Token: ", OAUTH_TOKEN); | ||
|
||
const EVENTSUB_WEBSOCKET_URL = 'wss://eventsub.wss.twitch.tv/ws'; | ||
var websocketClient; | ||
var websocketSessionID; | ||
var timeThen; | ||
|
||
// Start executing the bot from here | ||
function connectBot() { | ||
document.getElementById("token").disabled = true; | ||
document.getElementById("authorize").disabled = true; | ||
document.getElementById("username").disabled = true; | ||
document.getElementById("connect").disabled = true; | ||
document.getElementById("connect").innerHTML = "Connecting"; | ||
if (OAUTH_TOKEN) { | ||
(async () => { | ||
// Verify that the authentication is valid | ||
await getAuth(); | ||
await getUserId(); | ||
await getBotId(); | ||
|
||
// Start WebSocket client and register handlers | ||
startWebSocketClient(); | ||
})(); | ||
} else { | ||
console.error("No token provided") | ||
disconnectBot(); | ||
} | ||
|
||
// WebSocket will persist the application loop until you exit the program forcefully | ||
|
||
async function getUserId() { | ||
const response = await fetch(`https://api.twitch.tv/helix/users?login=${document.getElementById("username").value}`, { | ||
method: 'GET', | ||
headers: { | ||
'Client-Id': CLIENT_ID, | ||
'Authorization': 'Bearer ' + OAUTH_TOKEN | ||
} | ||
}); | ||
|
||
let data = await response.json(); | ||
if (response.status == 401) { | ||
console.error("No OAuth token provided"); | ||
disconnectBot(); | ||
}; | ||
try { | ||
CHAT_CHANNEL_USER_ID = data.data[0].id; | ||
console.log("Channel ID: ", CHAT_CHANNEL_USER_ID); | ||
} catch (error) { | ||
console.error("Invalid username, " + error); | ||
disconnectBot(); | ||
}; | ||
}; | ||
|
||
async function getBotId() { | ||
const response = await fetch('https://api.twitch.tv/helix/users', { | ||
method: 'GET', | ||
headers: { | ||
'Client-Id': CLIENT_ID, | ||
'Authorization': 'Bearer ' + OAUTH_TOKEN | ||
} | ||
}); | ||
|
||
let data = await response.json(); | ||
if (response.status == 401) { | ||
console.error("No OAuth token provided"); | ||
disconnectBot(); | ||
} | ||
try { | ||
BOT_USER_ID = data.data[0].id; | ||
console.log("Bot ID: ", BOT_USER_ID); | ||
} catch (error) { | ||
console.error(error); | ||
disconnectBot(); | ||
} | ||
} | ||
|
||
async function getAuth(token) { | ||
// https://dev.twitch.tv/docs/authentication/validate-tokens/#how-to-validate-a-token | ||
let response = await fetch('https://id.twitch.tv/oauth2/validate', { | ||
method: 'GET', | ||
headers: { | ||
'Authorization': 'OAuth ' + OAUTH_TOKEN | ||
} | ||
}); | ||
|
||
if (response.status != 200) { | ||
let data = await response.json(); | ||
console.error("Token is not valid. /oauth2/validate returned status code " + response.status); | ||
console.error(data); | ||
disconnectBot(); | ||
} | ||
|
||
console.log("Validated token."); | ||
} | ||
|
||
function startWebSocketClient() { | ||
websocketClient = new WebSocket(EVENTSUB_WEBSOCKET_URL); | ||
|
||
websocketClient.onerror = (console.error); | ||
|
||
websocketClient.onopen = () => { | ||
console.log('WebSocket connection opened to ' + EVENTSUB_WEBSOCKET_URL); | ||
}; | ||
|
||
websocketClient.onmessage = (data) => { | ||
console.log(data); | ||
try { | ||
handleWebSocketMessage(JSON.parse(data.data.toString())); | ||
} catch (error) { | ||
console.error("yo error") | ||
console.error(data.toString()); | ||
} | ||
}; | ||
} | ||
|
||
async function fetchData(type) { | ||
try { | ||
const url = "http://127.0.0.1:24050/json/v2" | ||
const response = await fetch(url) | ||
|
||
if (!response.ok) { | ||
throw new Error('it brokey', response.status); | ||
} | ||
|
||
const data = await response.json(); | ||
const beatmap = await data.beatmap; | ||
const play = await data.play; | ||
const state = await data.state; | ||
const profile = await data.profile; | ||
|
||
switch (type) { | ||
case ("mode"): | ||
let mode = beatmap.mode.number | ||
if (beatmap.mode.number == 0) { | ||
mode = profile.mode.number; | ||
} | ||
return mode; | ||
break; | ||
case ("map"): | ||
let modsName = ""; | ||
if (play.mods.name) { | ||
modsName = ` +${play.mods.name}` | ||
} | ||
let id = "(private map)"; | ||
if (beatmap.id) { | ||
id = `osu.pp.sh/b/${beatmap.id}` | ||
} | ||
let modeName = "" | ||
let modeNumber = beatmap.mode.number | ||
if (beatmap.mode.number == 0) { | ||
modeNumber = profile.mode.number; | ||
} | ||
switch (modeNumber) { | ||
case 0: | ||
modeName = "" | ||
break; | ||
case 1: | ||
modeName = " <osu!taiko>" | ||
break; | ||
case 2: | ||
modeName = " <osu!catch>" | ||
break; | ||
case 3: | ||
modeName = " <osu!mania>" | ||
break; | ||
} | ||
const mapFormatted = `${beatmap.artist} - ${beatmap.title} [${beatmap.version}] (${beatmap.mapper})${modeName}${modsName} | ${id}` | ||
return mapFormatted; | ||
break; | ||
case ("mods"): | ||
const modsNumber = play.mods.number; | ||
return modsNumber; | ||
break; | ||
case ("pp"): | ||
let currentPp = "" | ||
if (state.number == 2 || state.number == 7) { | ||
currentPp = ` ${play.pp.current.toFixed(2)}pp /` | ||
} | ||
return currentPp; | ||
} | ||
} | ||
|
||
catch (error) { | ||
console.error('it brokey but differently', error); | ||
}; | ||
} | ||
|
||
async function calculatePp(acc) { | ||
try { | ||
const response = await fetch(`http://127.0.0.1:24050/api/calculate/pp?mode=${await fetchData("mode")}&acc=${acc}&mods=${await fetchData("mods")}`) | ||
|
||
if (!response.ok) { | ||
throw new Error('it brokey', response.status); | ||
} | ||
|
||
const data = await response.json(); | ||
let ppAmount = await data.pp.toFixed(2); | ||
return ppAmount; | ||
} | ||
|
||
catch (error) { | ||
console.error('it brokey but differently', error); | ||
}; | ||
} | ||
|
||
async function handleWebSocketMessage(data) { | ||
switch (data.metadata.message_type) { | ||
case 'session_welcome': // First message you get from the WebSocket server when connecting | ||
websocketSessionID = data.payload.session.id; // Register the Session ID it gives us | ||
|
||
// Listen to EventSub, which joins the chatroom from your bot's account | ||
registerEventSubListeners(); | ||
break; | ||
case 'notification': // An EventSub notification has occurred, such as channel.chat.message | ||
switch (data.metadata.subscription_type) { | ||
case 'channel.chat.message': | ||
// First, print the message to the program's console. | ||
console.log(`MSG #${data.payload.event.broadcaster_user_login} <${data.payload.event.chatter_user_login}> ${data.payload.event.message.text}`); | ||
|
||
// Then check to see if that message was "HeyGuys" | ||
switch (data.payload.event.message.text.trim()) { | ||
// If so, send back "VoHiYo" to the chatroom | ||
case "!nppp": | ||
const timeNow = new Date().getTime() | ||
if ((!timeThen) || (timeNow - timeThen > document.getElementById("cooldown").value * 1000)) { | ||
sendChatMessage(`@${data.payload.event.chatter_user_name} | ${await fetchData("map")} |${await fetchData("pp")} 100%: ${await calculatePp(100)}pp, 99%: ${await calculatePp(99)}pp, 98%: ${await calculatePp(98)}pp, 95%: ${await calculatePp(95)}pp, 90%: ${await calculatePp(90)}pp`); | ||
timeThen = timeNow; | ||
} | ||
break; | ||
case "!np": | ||
if ((!timeThen) || (timeNow - timeThen > document.getElementById("cooldown").value * 1000)) { | ||
sendChatMessage(`@${data.payload.event.chatter_user_name} | ${await fetchData("map")}`) | ||
timeThen = timeNow; | ||
} | ||
break; | ||
} | ||
|
||
break; | ||
} | ||
break; | ||
} | ||
} | ||
|
||
async function sendChatMessage(chatMessage) { | ||
let response = await fetch('https://api.twitch.tv/helix/chat/messages', { | ||
method: 'POST', | ||
headers: { | ||
'Authorization': 'Bearer ' + OAUTH_TOKEN, | ||
'Client-Id': CLIENT_ID, | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ | ||
broadcaster_id: CHAT_CHANNEL_USER_ID, | ||
sender_id: BOT_USER_ID, | ||
message: chatMessage | ||
}) | ||
}); | ||
|
||
if (response.status != 200) { | ||
let data = await response.json(); | ||
console.error("Failed to send chat message"); | ||
console.error(data); | ||
} else { | ||
console.log("Sent chat message: " + chatMessage); | ||
} | ||
} | ||
|
||
async function registerEventSubListeners() { | ||
// Register channel.chat.message | ||
let response = await fetch('https://api.twitch.tv/helix/eventsub/subscriptions', { | ||
method: 'POST', | ||
headers: { | ||
'Authorization': 'Bearer ' + OAUTH_TOKEN, | ||
'Client-Id': CLIENT_ID, | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ | ||
type: 'channel.chat.message', | ||
version: '1', | ||
condition: { | ||
broadcaster_user_id: CHAT_CHANNEL_USER_ID, | ||
user_id: BOT_USER_ID | ||
}, | ||
transport: { | ||
method: 'websocket', | ||
session_id: websocketSessionID | ||
} | ||
}) | ||
}); | ||
|
||
if (response.status != 202) { | ||
let data = await response.json(); | ||
console.error("Failed to subscribe to channel.chat.message. API call returned status code " + response.status); | ||
console.error(data); | ||
disconnectBot(); | ||
} else { | ||
const data = await response.json(); | ||
let button = document.getElementById("connect") | ||
console.log(`Subscribed to channel.chat.message [${data.data[0].id}]`); | ||
button.disabled = false | ||
button.innerHTML = "Disconnect" | ||
button.onclick = function() { disconnectBot() }; | ||
} | ||
} | ||
async function disconnectBot() { | ||
let button = document.getElementById("connect") | ||
try { | ||
websocketClient.close(); | ||
console.log("Connection closed for " + EVENTSUB_WEBSOCKET_URL) | ||
} catch (error) { | ||
console.error("No WebSocket to close") | ||
} | ||
document.getElementById("token").disabled = false | ||
document.getElementById("authorize").disabled = false | ||
document.getElementById("username").disabled = false | ||
button.disabled = false | ||
button.innerHTML = "Connect" | ||
button.onclick = function() { connectBot() }; | ||
} | ||
} |
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 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>osu!nppp</title> | ||
<h1>osu!nppp - osu! twitch bot</h1> | ||
</head> | ||
<body> | ||
<form> | ||
<label for="token">Token:</label><br> | ||
<input type="password" id="token" name="token" readonly="true"> | ||
<a href="https://id.twitch.tv/oauth2/authorize?response_type=token&client_id=dl6k4oj0aiuyymn19opsclt6xyykbc&redirect_uri=https://jarran2r.github.io/osu-nppp&scope=user%3Abot+user%3Aread%3Achat+user%3Awrite%3Achat&force_verify=true"><button type="button" id="authorize">Authorize</button><br></a> | ||
<label for="username">Chat channel:</label><br> | ||
<input type="text" id="username" name="username"> | ||
<button type="button" onclick="connectBot()" id="connect">Connect</button><br> | ||
<label for="cooldown">Cooldown (seconds):</label><br> | ||
<input type="text" id="cooldown" name="cooldown" value="15"> | ||
</form> | ||
<p> | ||
so basically you click authorize and it'll bring you to a page to link the twitch account you wanna use for the bot (or just the streamer account like i like to do)<br> | ||
and then you just put the channel name for the channel you want to have it listen and respond to commands, then you click connect and it'll connect<br> | ||
<br> | ||
<h2>also everything is client-sided so nothing is stored on a server<br></h2> | ||
<h1>tosu must be running for this to work</h1> | ||
</p> | ||
<a href="https://github.com/Jarran2R/osu-nppp">GitHub</a> | ||
</body> | ||
<script src="bot.js"></script> | ||
</html> |