362 lines
12 KiB
JavaScript
362 lines
12 KiB
JavaScript
const osu = require("osu-packet"),
|
|
fs = require("fs"),
|
|
consoleHelper = require("../consoleHelper.js"),
|
|
packetIDs = require("./packetIDs.js"),
|
|
loginHandler = require("./loginHandler.js"),
|
|
parseUserData = require("./util/parseUserData.js"),
|
|
User = require("./User.js"),
|
|
getUserFromToken = require("./util/getUserByToken.js"),
|
|
getUserById = require("./util/getUserById.js"),
|
|
bakedResponses = require("./bakedResponses.js"),
|
|
Streams = require("./Streams.js"),
|
|
DatabaseHelperClass = require("./DatabaseHelper.js"),
|
|
funkyArray = require("./util/funkyArray.js"),
|
|
config = require("../config.json");
|
|
|
|
// Users funkyArray for session storage
|
|
global.users = new funkyArray();
|
|
|
|
// Add the bot user
|
|
global.botUser = global.users.add("bot", new User(3, "SillyBot", "bot"));
|
|
// Set the bot's position on the map
|
|
global.botUser.location[0] = 50;
|
|
global.botUser.location[1] = -32;
|
|
|
|
global.DatabaseHelper = new DatabaseHelperClass(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name);
|
|
|
|
async function subscribeToChannel(channelName = "", callback = function(message = "") {}) {
|
|
// Dup and connect new client for channel subscription (required)
|
|
const subscriptionClient = global.promClient.duplicate();
|
|
await subscriptionClient.connect();
|
|
// Subscribe to channel
|
|
await subscriptionClient.subscribe(channelName, callback);
|
|
consoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
|
}
|
|
|
|
// Do redis if it's enabled
|
|
if (config.redis.enabled) {
|
|
(async () => {
|
|
const { createClient } = require("redis");
|
|
global.promClient = createClient({
|
|
url: `redis://${config.redis.password.replaceAll(" ", "") == "" ? "" : `${config.redis.password}@`}${config.redis.address}:${config.redis.port}/${config.redis.database}`
|
|
});
|
|
|
|
global.promClient.on('error', e => consoleHelper.printRedis(e));
|
|
|
|
const connectionStartTime = Date.now();
|
|
await global.promClient.connect();
|
|
consoleHelper.printRedis(`Connected to redis server. Took ${Date.now() - connectionStartTime}ms`);
|
|
|
|
// Score submit update channel
|
|
subscribeToChannel("binato:update_user_stats", (message) => {
|
|
const user = getUserById(parseInt(message));
|
|
// Update user info
|
|
user.updateUserInfo(true);
|
|
|
|
consoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
|
});
|
|
})();
|
|
} else consoleHelper.printWarn("Redis is disabled!");
|
|
|
|
// User timeout interval
|
|
setInterval(() => {
|
|
for (let User of global.users.getIterableItems()) {
|
|
if (User.id == 3) continue; // Ignore the bot
|
|
// Bot: :(
|
|
|
|
// Logout this user, they're clearly gone.
|
|
if (Date.now() >= User.timeoutTime)
|
|
Logout(User);
|
|
}
|
|
}, 10000);
|
|
|
|
// An array containing the last 15 messages in chat
|
|
global.chatHistory = [];
|
|
global.addChatMessage = function(msg) {
|
|
if (global.chatHistory.length == 15) {
|
|
global.chatHistory.splice(0, 1);
|
|
global.chatHistory.push(msg);
|
|
} else {
|
|
global.chatHistory.push(msg);
|
|
}
|
|
}
|
|
|
|
global.StreamsHandler = new Streams();
|
|
|
|
// An array containing all chat channels
|
|
global.channels = [
|
|
{ channelName:"#osu", channelTopic:"The main channel", channelUserCount: 0, locked: false },
|
|
{ channelName:"#userlog", channelTopic:"Log about stuff doing go on yes very", channelUserCount: 0, locked: false },
|
|
{ channelName:"#lobby", channelTopic:"Talk about multiplayer stuff", channelUserCount: 0, locked: false },
|
|
{ channelName:"#english", channelTopic:"Talk in exclusively English", channelUserCount: 0, locked: false },
|
|
{ channelName:"#japanese", channelTopic:"Talk in exclusively Japanese", channelUserCount: 0, locked: false },
|
|
];
|
|
|
|
// Create a stream for each chat channel
|
|
for (let i = 0; i < global.channels.length; i++) {
|
|
global.StreamsHandler.addStream(global.channels[i].channelName, false);
|
|
}
|
|
|
|
// Add a stream for the multiplayer lobby
|
|
global.StreamsHandler.addStream("multiplayer_lobby", false);
|
|
|
|
if (!fs.existsSync("tHMM.ds")) fs.writeFileSync("tHMM.ds", "0");
|
|
global.totalHistoricalMultiplayerMatches = parseInt(fs.readFileSync("tHMM.ds").toString());
|
|
global.getAndAddToHistoricalMultiplayerMatches = function() {
|
|
global.totalHistoricalMultiplayerMatches++;
|
|
fs.writeFile("tHMM.ds", `${global.totalHistoricalMultiplayerMatches}`, () => {});
|
|
return global.totalHistoricalMultiplayerMatches;
|
|
}
|
|
|
|
// Include packets
|
|
const ChangeAction = require("./Packets/ChangeAction.js"),
|
|
SendPublicMessage = require("./Packets/SendPublicMessage.js"),
|
|
Logout = require("./Packets/Logout.js"),
|
|
Spectator = require("./Spectator.js"),
|
|
SendPrivateMessage = require("./Packets/SendPrivateMessage.js"),
|
|
MultiplayerManager = require("./MultiplayerManager.js"),
|
|
SetAwayMessage = require("./Packets/SetAwayMessage.js"),
|
|
ChannelJoin = require("./Packets/ChannelJoin.js"),
|
|
ChannelPart = require("./Packets/ChannelPart.js"),
|
|
AddFriend = require("./Packets/AddFriend.js"),
|
|
RemoveFriend = require("./Packets/RemoveFriend.js"),
|
|
UserPresenceBundle = require("./Packets/UserPresenceBundle.js"),
|
|
UserPresence = require("./Packets/UserPresence.js"),
|
|
UserStatsRequest = require("./Packets/UserStatsRequest.js"),
|
|
MultiplayerInvite = require("./Packets/MultiplayerInvite.js"),
|
|
TourneyMatchSpecialInfo = require("./Packets/TourneyMatchSpecialInfo.js"),
|
|
TourneyMatchJoinChannel = require("./Packets/TourneyMatchSpecialInfo.js"),
|
|
TourneyMatchLeaveChannel = require("./Packets/TourneyLeaveMatchChannel.js");
|
|
|
|
// A class for managing everything multiplayer
|
|
global.MultiplayerManager = new MultiplayerManager();
|
|
|
|
module.exports = async function(req, res) {
|
|
// Get the client's token string and request data
|
|
const requestTokenString = req.header("osu-token"),
|
|
requestData = req.packet;
|
|
|
|
// Server's response
|
|
let responseData;
|
|
|
|
// Check if the user is logged in
|
|
if (requestTokenString == null) {
|
|
// Client doesn't have a token yet, let's auth them!
|
|
const userData = parseUserData(requestData);
|
|
consoleHelper.printBancho(`New client connection. [User: ${userData.username}]`);
|
|
await loginHandler(req, res, userData);
|
|
} else {
|
|
// Client has a token, let's see what they want.
|
|
try {
|
|
// Get the current user
|
|
const PacketUser = getUserFromToken(requestTokenString);
|
|
|
|
// Make sure the client's token isn't invalid
|
|
if (PacketUser != null) {
|
|
// Update the session timeout time
|
|
PacketUser.timeoutTime = Date.now() + 60000;
|
|
|
|
// Create a new osu! packet reader
|
|
const osuPacketReader = new osu.Client.Reader(requestData);
|
|
// Parse current bancho packet
|
|
const PacketData = osuPacketReader.Parse();
|
|
|
|
// Go through each packet sent by the client
|
|
PacketData.forEach(CurrentPacket => {
|
|
switch (CurrentPacket.id) {
|
|
case packetIDs.client_changeAction:
|
|
ChangeAction(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_sendPublicMessage:
|
|
SendPublicMessage(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_logout:
|
|
Logout(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_requestStatusUpdate:
|
|
UserPresenceBundle(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_pong: // Pretty sure this is just a client ping
|
|
// so we probably don't do anything here
|
|
break; // It's probably just the client wanting to pull data down. (That's exactly what it is)
|
|
|
|
case packetIDs.client_startSpectating:
|
|
Spectator.startSpectatingUser(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_spectateFrames:
|
|
Spectator.sendSpectatorFrames(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_stopSpectating:
|
|
Spectator.stopSpectatingUser(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_sendPrivateMessage:
|
|
SendPrivateMessage(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_joinLobby:
|
|
global.MultiplayerManager.userEnterLobby(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_partLobby:
|
|
global.MultiplayerManager.userLeaveLobby(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_createMatch:
|
|
global.MultiplayerManager.createMultiplayerMatch(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_joinMatch:
|
|
global.MultiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_matchChangeSlot:
|
|
PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_matchReady:
|
|
PacketUser.currentMatch.setStateReady(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_matchChangeSettings:
|
|
PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_matchNotReady:
|
|
PacketUser.currentMatch.setStateNotReady(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_partMatch:
|
|
global.MultiplayerManager.leaveMultiplayerMatch(PacketUser);
|
|
break;
|
|
|
|
// Also handles user kick if the slot has a user
|
|
case packetIDs.client_matchLock:
|
|
PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_matchNoBeatmap:
|
|
PacketUser.currentMatch.missingBeatmap(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_matchSkipRequest:
|
|
PacketUser.currentMatch.matchSkip(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_matchHasBeatmap:
|
|
PacketUser.currentMatch.notMissingBeatmap(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_matchTransferHost:
|
|
PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_matchChangeMods:
|
|
PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_matchStart:
|
|
PacketUser.currentMatch.startMatch();
|
|
break;
|
|
|
|
case packetIDs.client_matchLoadComplete:
|
|
PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_matchComplete:
|
|
PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_matchScoreUpdate:
|
|
PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_matchFailed:
|
|
PacketUser.currentMatch.matchFailed(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_matchChangeTeam:
|
|
PacketUser.currentMatch.changeTeam(PacketUser);
|
|
break;
|
|
|
|
case packetIDs.client_channelJoin:
|
|
ChannelJoin(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_channelPart:
|
|
ChannelPart(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_setAwayMessage:
|
|
SetAwayMessage(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_friendAdd:
|
|
AddFriend(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_friendRemove:
|
|
RemoveFriend(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_userStatsRequest:
|
|
UserStatsRequest(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_specialMatchInfoRequest:
|
|
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_specialJoinMatchChannel:
|
|
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_specialLeaveMatchChannel:
|
|
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_invite:
|
|
MultiplayerInvite(PacketUser, CurrentPacket.data);
|
|
break;
|
|
|
|
case packetIDs.client_userPresenceRequest:
|
|
UserPresence(PacketUser, PacketUser.id); // Can't really think of a way to generalize this?
|
|
break;
|
|
|
|
default:
|
|
// Ignore client_beatmapInfoRequest and client_receiveUpdates
|
|
if (CurrentPacket.id == 68 || CurrentPacket.id == 79) break;
|
|
// Print out unimplemented packet
|
|
console.dir(CurrentPacket);
|
|
break;
|
|
}
|
|
});
|
|
|
|
responseData = PacketUser.queue
|
|
PacketUser.clearQueue();
|
|
} else {
|
|
// User's token is invlid, force a reconnect
|
|
consoleHelper.printBancho(`Forced client re-login (Token is invalid)`);
|
|
responseData = bakedResponses("reconnect");
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
} finally {
|
|
// Only send the headers that we absolutely have to
|
|
res.removeHeader('X-Powered-By');
|
|
res.removeHeader('Date');
|
|
res.writeHead(200, {
|
|
"Connection": "keep-alive",
|
|
"Keep-Alive": "timeout=5, max=100",
|
|
});
|
|
// Send the prepared packet(s) to the client
|
|
res.end(responseData);
|
|
}
|
|
}
|
|
};
|