Compare commits

...

20 commits

Author SHA1 Message Date
b40361caf0 Replace fileSmasher with ncc. 2023-10-16 10:52:36 +01:00
640c2cdf92 Send friends list info after login 2023-10-10 16:03:55 +01:00
9d53d82997
DB access cleanup 2023-10-07 13:09:10 +01:00
877592bb94 Move all packet interfaces to a better location 2023-10-06 11:19:08 +01:00
2fbdb9799a Remove last instance of explicit any type 2023-10-06 09:55:15 +01:00
a32ab80f73 General cleanup 2023-10-06 09:55:03 +01:00
4ec4cb1c1f Remove duplicate interface 2023-10-06 09:52:22 +01:00
5e1106e488 Give types to all functions in OsuPacketWriter 2023-10-06 09:48:31 +01:00
39f6669f94 add type 2023-10-05 11:15:13 +01:00
056260ad55 improve tooling 2023-10-05 11:13:19 +01:00
78f4a499fa oops 2023-10-04 16:21:29 +01:00
462d0c879c Consolidate admin commands 2023-10-04 16:13:16 +01:00
108f27eb22 Remove unneeded semicolons 2023-10-04 15:52:18 +01:00
8ab318ef12 Correct some mistakes in the DB class 2023-10-04 15:12:35 +01:00
4b90031294 Kickstart the big db rewrite 2023-10-04 15:10:38 +01:00
686e6001b2 Code quality improvements. 2023-10-04 12:28:47 +01:00
93da399fa5 General code quality/readability 2023-10-03 10:09:44 +01:00
04bd1e42bb Change all for(each) blocks to use const instead of let. 2023-10-03 09:57:35 +01:00
25105537ea
Fix rate adjust mods not working in multi when freemod is active 2023-09-13 08:31:16 +01:00
c3b24d32af
Re-add LICENSE 2023-09-12 15:11:34 +01:00
66 changed files with 946 additions and 579 deletions

View file

@ -6,7 +6,7 @@ enum LogType {
INFO, INFO,
WARN, WARN,
ERROR ERROR
}; }
const LogTags = { const LogTags = {
INFO: dyetty.bgGreen(dyetty.black(" INFO ")), INFO: dyetty.bgGreen(dyetty.black(" INFO ")),

21
LICENSE.txt Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-2023 Holly Stubbs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -10,7 +10,7 @@ export default abstract class osu {
}; };
static Client = { static Client = {
Reader: function(data:any) : any { Reader: function(data:Buffer) {
return new nodeOsu.Client.Reader(data); return new nodeOsu.Client.Reader(data);
} }
}; };

29
package-lock.json generated
View file

@ -20,9 +20,11 @@
"devDependencies": { "devDependencies": {
"@types/node": "^20.6.0", "@types/node": "^20.6.0",
"@types/node-fetch": "^2.6.4", "@types/node-fetch": "^2.6.4",
"@vercel/ncc": "^0.38.0",
"check-outdated": "^2.12.0", "check-outdated": "^2.12.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"terser": "^5.21.0",
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.2.2" "typescript": "^5.2.2"
@ -45,7 +47,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.0.1", "@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/sourcemap-codec": "^1.4.10",
@ -69,7 +70,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -79,7 +79,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9" "@jridgewell/trace-mapping": "^0.3.9"
@ -230,6 +229,15 @@
"form-data": "^3.0.0" "form-data": "^3.0.0"
} }
}, },
"node_modules/@vercel/ncc": {
"version": "0.38.0",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.0.tgz",
"integrity": "sha512-B4YKZMm/EqMptKSFyAq4q2SlgJe+VCmEH6Y8gf/E1pTlWbsUJpuH1ymik2Ex3aYO5mCWwV1kaSYHSQOT8+4vHA==",
"dev": true,
"bin": {
"ncc": "dist/ncc/cli.js"
}
},
"node_modules/@webassemblyjs/ast": { "node_modules/@webassemblyjs/ast": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
@ -635,8 +643,7 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.2", "version": "1.0.2",
@ -774,8 +781,7 @@
"version": "2.20.3", "version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
@ -2376,7 +2382,6 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -2386,7 +2391,6 @@
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
"source-map": "^0.6.0" "source-map": "^0.6.0"
@ -2545,11 +2549,10 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.19.2", "version": "5.21.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz",
"integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2", "acorn": "^8.8.2",

View file

@ -7,8 +7,7 @@
"dev:updateCheck": "check-outdated", "dev:updateCheck": "check-outdated",
"dev:run": "nodemon --watch './**/*.ts' Binato.ts", "dev:run": "nodemon --watch './**/*.ts' Binato.ts",
"build": "npm-run-all build:*", "build": "npm-run-all build:*",
"build:smash": "ts-node ./tooling/fileSmasher.ts", "build:build": "ncc build Binato.ts -o build",
"build:build": "tsc --build",
"build:mangle": "ts-node ./tooling/mangle.ts", "build:mangle": "ts-node ./tooling/mangle.ts",
"build:cleanup": "ts-node ./tooling/cleanup.ts", "build:cleanup": "ts-node ./tooling/cleanup.ts",
"_clean": "tsc --build --clean" "_clean": "tsc --build --clean"
@ -28,9 +27,11 @@
"devDependencies": { "devDependencies": {
"@types/node": "^20.6.0", "@types/node": "^20.6.0",
"@types/node-fetch": "^2.6.4", "@types/node-fetch": "^2.6.4",
"@vercel/ncc": "^0.38.0",
"check-outdated": "^2.12.0", "check-outdated": "^2.12.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"terser": "^5.21.0",
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.2.2" "typescript": "^5.2.2"

View file

@ -5,15 +5,15 @@ import LoginProcess from "./LoginProcess";
import { IncomingMessage, ServerResponse } from "http"; import { IncomingMessage, ServerResponse } from "http";
import { Packets } from "./enums/Packets"; import { Packets } from "./enums/Packets";
import { RedisClientType, createClient } from "redis"; import { RedisClientType, createClient } from "redis";
import MessageData from "./interfaces/MessageData"; import MessageData from "./interfaces/packetTypes/MessageData";
import PrivateMessage from "./packets/PrivateMessage"; import PrivateMessage from "./packets/PrivateMessage";
import Shared from "./objects/Shared"; import Shared from "./objects/Shared";
import SpectatorManager from "./SpectatorManager"; import SpectatorManager from "./SpectatorManager";
import osu from "../osuTyping"; import osu from "../osuTyping";
const shared:Shared = new Shared(); const shared:Shared = new Shared();
shared.database.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL"); shared.database.execute("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
shared.database.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'"); shared.database.execute("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
// Server Setup // Server Setup
const spectatorManager:SpectatorManager = new SpectatorManager(shared); const spectatorManager:SpectatorManager = new SpectatorManager(shared);
@ -67,10 +67,11 @@ import AddFriend from "./packets/AddFriend";
import RemoveFriend from "./packets/RemoveFriend"; import RemoveFriend from "./packets/RemoveFriend";
import PrivateChannel from "./objects/PrivateChannel"; import PrivateChannel from "./objects/PrivateChannel";
import MultiplayerInvite from "./packets/MultiplayerInvite"; import MultiplayerInvite from "./packets/MultiplayerInvite";
import SendPublicMessage from "./packets/SendPublicMessage";
// User timeout interval // User timeout interval
setInterval(() => { setInterval(() => {
for (let User of shared.users.getIterableItems()) { for (const User of shared.users.getIterableItems()) {
if (User.uuid == "bot") continue; // Ignore the bot if (User.uuid == "bot") continue; // Ignore the bot
// Logout this user, they're clearly gone. // Logout this user, they're clearly gone.
@ -80,8 +81,6 @@ setInterval(() => {
} }
}, 10000); }, 10000);
const EMPTY_BUFFER = Buffer.alloc(0);
export default async function HandleRequest(req:IncomingMessage, res:ServerResponse, packet:Buffer) { export default async function HandleRequest(req:IncomingMessage, res:ServerResponse, packet:Buffer) {
// Get the client's token string and request data // Get the client's token string and request data
const requestTokenString = typeof(req.headers["osu-token"]) === "string" ? req.headers["osu-token"] : undefined; const requestTokenString = typeof(req.headers["osu-token"]) === "string" ? req.headers["osu-token"] : undefined;
@ -91,154 +90,149 @@ export default async function HandleRequest(req:IncomingMessage, res:ServerRespo
// Client doesn't have a token yet, let's auth them! // Client doesn't have a token yet, let's auth them!
await LoginProcess(req, res, packet, shared); await LoginProcess(req, res, packet, shared);
shared.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [shared.users.getLength() - 1]); shared.database.execute("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [shared.users.getLength() - 1]);
} else { } else {
let responseData = EMPTY_BUFFER; let responseData = Buffer.allocUnsafe(0);
// Client has a token, let's see what they want. // Client has a token, let's see what they want.
try { try {
// Get the current user // Get the current user
const PacketUser = shared.users.getByToken(requestTokenString); const user = shared.users.getByToken(requestTokenString);
// Make sure the client's token isn't invalid // Make sure the client's token isn't invalid
if (PacketUser != null) { if (user != null) {
// Update the session timeout time // Update the session timeout time for each request
PacketUser.timeoutTime = Date.now() + 60000; user.timeoutTime = Date.now() + 60000;
// Create a new osu! packet reader // Parse bancho packets
const osuPacketReader = osu.Client.Reader(packet); const osuPacketReader = osu.Client.Reader(packet);
// Parse current bancho packet const packets = osuPacketReader.Parse();
const PacketData = osuPacketReader.Parse();
// Go through each packet sent by the client // Go through each packet sent by the client
for (let CurrentPacket of PacketData) { for (const packet of packets) {
switch (CurrentPacket.id) { switch (packet.id) {
case Packets.Client_ChangeAction: case Packets.Client_ChangeAction:
ChangeAction(PacketUser, CurrentPacket.data); ChangeAction(user, packet.data);
break; break;
case Packets.Client_SendPublicMessage: case Packets.Client_SendPublicMessage:
const message:MessageData = CurrentPacket.data; SendPublicMessage(user, packet.data);
let channel = shared.chatManager.GetChannelByName(message.target);
if (channel instanceof Channel) {
channel.SendMessage(PacketUser, CurrentPacket.data.message);
}
break; break;
case Packets.Client_Logout: case Packets.Client_Logout:
await Logout(PacketUser); await Logout(user);
break; break;
case Packets.Client_RequestStatusUpdate: case Packets.Client_RequestStatusUpdate:
UserPresenceBundle(PacketUser); UserPresenceBundle(user);
break; break;
case Packets.Client_StartSpectating: case Packets.Client_StartSpectating:
spectatorManager.startSpectating(PacketUser, CurrentPacket.data); spectatorManager.startSpectating(user, packet.data);
break; break;
case Packets.Client_SpectateFrames: case Packets.Client_SpectateFrames:
spectatorManager.spectatorFrames(PacketUser, CurrentPacket.data); spectatorManager.spectatorFrames(user, packet.data);
break; break;
case Packets.Client_StopSpectating: case Packets.Client_StopSpectating:
spectatorManager.stopSpectating(PacketUser); spectatorManager.stopSpectating(user);
break; break;
case Packets.Client_SendPrivateMessage: case Packets.Client_SendPrivateMessage:
PrivateMessage(PacketUser, CurrentPacket.data); PrivateMessage(user, packet.data);
break; break;
case Packets.Client_JoinLobby: case Packets.Client_JoinLobby:
shared.multiplayerManager.JoinLobby(PacketUser); shared.multiplayerManager.JoinLobby(user);
break; break;
case Packets.Client_PartLobby: case Packets.Client_PartLobby:
shared.multiplayerManager.LeaveLobby(PacketUser); shared.multiplayerManager.LeaveLobby(user);
break; break;
case Packets.Client_CreateMatch: case Packets.Client_CreateMatch:
await shared.multiplayerManager.CreateMatch(PacketUser, CurrentPacket.data); await shared.multiplayerManager.CreateMatch(user, packet.data);
break; break;
case Packets.Client_JoinMatch: case Packets.Client_JoinMatch:
shared.multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data); shared.multiplayerManager.JoinMatch(user, packet.data);
break; break;
case Packets.Client_MatchChangeSlot: case Packets.Client_MatchChangeSlot:
PacketUser.match?.moveToSlot(PacketUser, CurrentPacket.data); user.match?.moveToSlot(user, packet.data);
break; break;
case Packets.Client_MatchReady: case Packets.Client_MatchReady:
PacketUser.match?.setStateReady(PacketUser); user.match?.setStateReady(user);
break; break;
case Packets.Client_MatchChangeSettings: case Packets.Client_MatchChangeSettings:
await PacketUser.match?.updateMatch(PacketUser, CurrentPacket.data); await user.match?.updateMatch(user, packet.data);
break; break;
case Packets.Client_MatchNotReady: case Packets.Client_MatchNotReady:
PacketUser.match?.setStateNotReady(PacketUser); user.match?.setStateNotReady(user);
break; break;
case Packets.Client_PartMatch: case Packets.Client_PartMatch:
await shared.multiplayerManager.LeaveMatch(PacketUser); await shared.multiplayerManager.LeaveMatch(user);
break; break;
case Packets.Client_MatchLock: case Packets.Client_MatchLock:
PacketUser.match?.lockOrKick(PacketUser, CurrentPacket.data); user.match?.lockOrKick(user, packet.data);
break; break;
case Packets.Client_MatchNoBeatmap: case Packets.Client_MatchNoBeatmap:
PacketUser.match?.missingBeatmap(PacketUser); user.match?.missingBeatmap(user);
break; break;
case Packets.Client_MatchSkipRequest: case Packets.Client_MatchSkipRequest:
PacketUser.match?.matchSkip(PacketUser); user.match?.matchSkip(user);
break; break;
case Packets.Client_MatchHasBeatmap: case Packets.Client_MatchHasBeatmap:
PacketUser.match?.notMissingBeatmap(PacketUser); user.match?.notMissingBeatmap(user);
break; break;
case Packets.Client_MatchTransferHost: case Packets.Client_MatchTransferHost:
PacketUser.match?.transferHost(PacketUser, CurrentPacket.data); user.match?.transferHost(user, packet.data);
break; break;
case Packets.Client_MatchChangeMods: case Packets.Client_MatchChangeMods:
PacketUser.match?.updateMods(PacketUser, CurrentPacket.data); user.match?.updateMods(user, packet.data);
break; break;
case Packets.Client_MatchStart: case Packets.Client_MatchStart:
PacketUser.match?.startMatch(); user.match?.startMatch();
break; break;
case Packets.Client_MatchLoadComplete: case Packets.Client_MatchLoadComplete:
PacketUser.match?.matchPlayerLoaded(PacketUser); user.match?.matchPlayerLoaded(user);
break; break;
case Packets.Client_MatchComplete: case Packets.Client_MatchComplete:
await PacketUser.match?.onPlayerFinishMatch(PacketUser); await user.match?.onPlayerFinishMatch(user);
break; break;
case Packets.Client_MatchScoreUpdate: case Packets.Client_MatchScoreUpdate:
PacketUser.match?.updatePlayerScore(PacketUser, CurrentPacket.data); user.match?.updatePlayerScore(user, packet.data);
break; break;
case Packets.Client_MatchFailed: case Packets.Client_MatchFailed:
PacketUser.match?.matchFailed(PacketUser); user.match?.matchFailed(user);
break; break;
case Packets.Client_MatchChangeTeam: case Packets.Client_MatchChangeTeam:
PacketUser.match?.changeTeam(PacketUser); user.match?.changeTeam(user);
break; break;
case Packets.Client_ChannelJoin: case Packets.Client_ChannelJoin:
PacketUser.joinChannel(CurrentPacket.data); user.joinChannel(packet.data);
break; break;
case Packets.Client_ChannelPart: case Packets.Client_ChannelPart:
PacketUser.leaveChannel(CurrentPacket.data); user.leaveChannel(packet.data);
break; break;
case Packets.Client_SetAwayMessage: case Packets.Client_SetAwayMessage:
@ -246,35 +240,35 @@ export default async function HandleRequest(req:IncomingMessage, res:ServerRespo
break; break;
case Packets.Client_FriendAdd: case Packets.Client_FriendAdd:
AddFriend(PacketUser, CurrentPacket.data); await AddFriend(user, packet.data);
break; break;
case Packets.Client_FriendRemove: case Packets.Client_FriendRemove:
RemoveFriend(PacketUser, CurrentPacket.data); await RemoveFriend(user, packet.data);
break; break;
case Packets.Client_UserStatsRequest: case Packets.Client_UserStatsRequest:
UserStatsRequest(PacketUser, CurrentPacket.data); UserStatsRequest(user, packet.data);
break; break;
case Packets.Client_SpecialMatchInfoRequest: case Packets.Client_SpecialMatchInfoRequest:
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data); TourneyMatchSpecialInfo(user, packet.data);
break; break;
case Packets.Client_SpecialJoinMatchChannel: case Packets.Client_SpecialJoinMatchChannel:
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data); TourneyMatchJoinChannel(user, packet.data);
break; break;
case Packets.Client_SpecialLeaveMatchChannel: case Packets.Client_SpecialLeaveMatchChannel:
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data); TourneyMatchLeaveChannel(user, packet.data);
break; break;
case Packets.Client_Invite: case Packets.Client_Invite:
MultiplayerInvite(PacketUser, CurrentPacket.data); MultiplayerInvite(user, packet.data);
break; break;
case Packets.Client_UserPresenceRequest: case Packets.Client_UserPresenceRequest:
UserPresence(PacketUser, PacketUser.id); UserPresence(user, user.id);
break; break;
// Ignored packets // Ignored packets
@ -286,13 +280,13 @@ export default async function HandleRequest(req:IncomingMessage, res:ServerRespo
default: default:
// Print out unimplemented packet // Print out unimplemented packet
console.dir(CurrentPacket); console.dir(packet);
break; break;
} }
} }
responseData = PacketUser.queue; responseData = user.queue;
PacketUser.clearQueue(); user.clearQueue();
} else { } else {
// User's token is invlid, force a reconnect // User's token is invlid, force a reconnect
ConsoleHelper.printBancho(`Forced client re-connect (Token is invalid)`); ConsoleHelper.printBancho(`Forced client re-connect (Token is invalid)`);

View file

@ -5,7 +5,7 @@ import User from "./objects/User";
// Commands // Commands
import RankingCommand from "./commands/Ranking"; import RankingCommand from "./commands/Ranking";
import LockCommand from "./commands/Lock"; import AdminCommand from "./commands/Admin";
import MultiplayerCommands from "./commands/Multiplayer"; import MultiplayerCommands from "./commands/Multiplayer";
import HelpCommand from "./commands/Help"; import HelpCommand from "./commands/Help";
import RollCommand from "./commands/Roll"; import RollCommand from "./commands/Roll";
@ -19,7 +19,7 @@ export default class Bot {
this.commands["help"] = new HelpCommand(shared, this.commands); this.commands["help"] = new HelpCommand(shared, this.commands);
this.commands["ranking"] = new RankingCommand(shared); this.commands["ranking"] = new RankingCommand(shared);
this.commands["lock"] = new LockCommand(shared); this.commands["admin"] = new AdminCommand(shared);
this.commands["mp"] = new MultiplayerCommands(shared); this.commands["mp"] = new MultiplayerCommands(shared);
this.commands["roll"] = new RollCommand(shared); this.commands["roll"] = new RollCommand(shared);
} }

View file

@ -69,7 +69,7 @@ export default class ChatManager {
} }
public ForceJoinChannels(user:User) { public ForceJoinChannels(user:User) {
for (let channel of this.forceJoinChannels.getIterableItems()) { for (const channel of this.forceJoinChannels.getIterableItems()) {
channel.Join(user); channel.Join(user);
} }
} }
@ -77,7 +77,7 @@ export default class ChatManager {
public SendChannelListing(user:User) { public SendChannelListing(user:User) {
const osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
for (let channel of this.chatChannels.getIterableItems()) { for (const channel of this.chatChannels.getIterableItems()) {
if (channel.isSpecial) { if (channel.isSpecial) {
continue; continue;
} }

View file

@ -1,269 +1,261 @@
enum CountryCodes { const countryCodes:{ [id: string]: number } = {
LV = 132, "LV": 132,
AD = 3, "AD": 3,
LT = 130, "LT": 130,
KM = 116, "KM": 116,
QA = 182, "QA": 182,
VA = 0, "VA": 0,
PK = 173, "PK": 173,
KI = 115, "KI": 115,
SS = 0, "SS": 0,
KH = 114, "KH": 114,
NZ = 166, "NZ": 166,
TO = 215, "TO": 215,
KZ = 122, "KZ": 122,
BW = 35, "BW": 35,
GA = 76, "GA": 76,
AX = 247, "AX": 247,
GE = 79, "GE": 79,
UA = 222, "UA": 222,
CR = 50, "CR": 50,
AE = 0, "AE": 0,
NE = 157, "NE": 157,
ZA = 240, "ZA": 240,
SK = 196, "SK": 196,
BV = 34, "BV": 34,
SH = 0, "SH": 0,
PT = 179, "PT": 179,
SC = 189, "SC": 189,
CO = 49, "CO": 49,
GP = 86, "GP": 86,
GY = 93, "GY": 93,
CM = 47, "CM": 47,
TJ = 211, "TJ": 211,
AF = 5, "AF": 5,
IE = 101, "IE": 101,
AL = 8, "AL": 8,
BG = 24, "BG": 24,
JO = 110, "JO": 110,
MU = 149, "MU": 149,
PM = 0, "PM": 0,
LA = 0, "LA": 0,
IO = 104, "IO": 104,
KY = 121, "KY": 121,
SA = 187, "SA": 187,
KN = 0, "KN": 0,
OM = 167, "OM": 167,
CY = 54, "CY": 54,
BQ = 0, "BQ": 0,
BT = 33, "BT": 33,
WS = 236, "WS": 236,
ES = 67, "ES": 67,
LR = 128, "LR": 128,
RW = 186, "RW": 186,
AQ = 12, "AQ": 12,
PW = 180, "PW": 180,
JE = 250, "JE": 250,
TN = 214, "TN": 214,
ZW = 243, "ZW": 243,
JP = 111, "JP": 111,
BB = 20, "BB": 20,
VN = 233, "VN": 233,
HN = 96, "HN": 96,
KP = 0, "KP": 0,
WF = 235, "WF": 235,
EC = 62, "EC": 62,
HU = 99, "HU": 99,
GF = 80, "GF": 80,
GQ = 87, "GQ": 87,
TW = 220, "TW": 220,
MC = 135, "MC": 135,
BE = 22, "BE": 22,
PN = 176, "PN": 176,
SZ = 205, "SZ": 205,
CZ = 55, "CZ": 55,
LY = 0, "LY": 0,
IN = 103, "IN": 103,
FM = 0, "FM": 0,
PY = 181, "PY": 181,
PH = 172, "PH": 172,
MN = 142, "MN": 142,
GG = 248, "GG": 248,
CC = 39, "CC": 39,
ME = 242, "ME": 242,
DO = 60, "DO": 60,
KR = 0, "KR": 0,
PL = 174, "PL": 174,
MT = 148, "MT": 148,
MM = 141, "MM": 141,
AW = 17, "AW": 17,
MV = 150, "MV": 150,
BD = 21, "BD": 21,
NR = 164, "NR": 164,
AT = 15, "AT": 15,
GW = 92, "GW": 92,
FR = 74, "FR": 74,
LI = 126, "LI": 126,
CF = 41, "CF": 41,
DZ = 61, "DZ": 61,
MA = 134, "MA": 134,
VG = 0, "VG": 0,
NC = 156, "NC": 156,
IQ = 105, "IQ": 105,
BN = 0, "BN": 0,
BF = 23, "BF": 23,
BO = 30, "BO": 30,
GB = 77, "GB": 77,
CU = 51, "CU": 51,
LU = 131, "LU": 131,
YT = 238, "YT": 238,
NO = 162, "NO": 162,
SM = 198, "SM": 198,
GL = 83, "GL": 83,
IS = 107, "IS": 107,
AO = 11, "AO": 11,
MH = 138, "MH": 138,
SE = 191, "SE": 191,
ZM = 241, "ZM": 241,
FJ = 70, "FJ": 70,
SL = 197, "SL": 197,
CH = 43, "CH": 43,
RU = 0, "RU": 0,
CW = 0, "CW": 0,
CX = 53, "CX": 53,
TF = 208, "TF": 208,
NL = 161, "NL": 161,
AU = 16, "AU": 16,
FI = 69, "FI": 69,
MS = 147, "MS": 147,
GH = 81, "GH": 81,
BY = 36, "BY": 36,
IL = 102, "IL": 102,
VC = 0, "VC": 0,
NG = 159, "NG": 159,
HT = 98, "HT": 98,
LS = 129, "LS": 129,
MR = 146, "MR": 146,
YE = 237, "YE": 237,
MP = 144, "MP": 144,
SX = 0, "SX": 0,
RE = 183, "RE": 183,
RO = 184, "RO": 184,
NP = 163, "NP": 163,
CG = 0, "CG": 0,
FO = 73, "FO": 73,
CI = 0, "CI": 0,
TH = 210, "TH": 210,
HK = 94, "HK": 94,
TK = 212, "TK": 212,
XK = 0, "XK": 0,
DM = 59, "DM": 59,
LC = 0, "LC": 0,
ID = 100, "ID": 100,
MG = 137, "MG": 137,
JM = 109, "JM": 109,
IT = 108, "IT": 108,
CA = 38, "CA": 38,
TZ = 221, "TZ": 221,
GI = 82, "GI": 82,
KG = 113, "KG": 113,
NU = 165, "NU": 165,
TV = 219, "TV": 219,
LB = 124, "LB": 124,
SY = 0, "SY": 0,
PR = 177, "PR": 177,
NI = 160, "NI": 160,
KE = 112, "KE": 112,
MO = 0, "MO": 0,
SR = 201, "SR": 201,
VI = 0, "VI": 0,
SV = 203, "SV": 203,
HM = 0, "HM": 0,
CD = 0, "CD": 0,
BI = 26, "BI": 26,
BM = 28, "BM": 28,
MW = 151, "MW": 151,
TM = 213, "TM": 213,
GT = 90, "GT": 90,
AG = 0, "AG": 0,
UM = 0, "UM": 0,
US = 225, "US": 225,
AR = 13, "AR": 13,
DJ = 57, "DJ": 57,
KW = 120, "KW": 120,
MY = 153, "MY": 153,
FK = 71, "FK": 71,
EG = 64, "EG": 64,
BA = 0, "BA": 0,
CN = 48, "CN": 48,
GN = 85, "GN": 85,
PS = 178, "PS": 178,
SO = 200, "SO": 200,
IM = 249, "IM": 249,
GS = 0, "GS": 0,
BR = 31, "BR": 31,
GM = 84, "GM": 84,
PF = 170, "PF": 170,
PA = 168, "PA": 168,
PG = 171, "PG": 171,
BH = 25, "BH": 25,
TG = 209, "TG": 209,
GU = 91, "GU": 91,
CK = 45, "CK": 45,
MF = 252, "MF": 252,
VE = 230, "VE": 230,
CL = 46, "CL": 46,
TR = 217, "TR": 217,
UG = 223, "UG": 223,
GD = 78, "GD": 78,
TT = 218, "TT": 218,
TL = 0, "TL": 0,
MD = 0, "MD": 0,
MK = 0, "MK": 0,
ST = 202, "ST": 202,
CV = 52, "CV": 52,
MQ = 145, "MQ": 145,
GR = 88, "GR": 88,
HR = 97, "HR": 97,
BZ = 37, "BZ": 37,
UZ = 227, "UZ": 227,
DK = 58, "DK": 58,
SN = 199, "SN": 199,
ET = 68, "ET": 68,
VU = 234, "VU": 234,
ER = 66, "ER": 66,
BJ = 27, "BJ": 27,
LK = 127, "LK": 127,
NA = 155, "NA": 155,
AS = 14, "AS": 14,
SG = 192, "SG": 192,
PE = 169, "PE": 169,
IR = 0, "IR": 0,
MX = 152, "MX": 152,
TD = 207, "TD": 207,
AZ = 18, "AZ": 18,
AM = 9, "AM": 9,
BL = 0, "BL": 0,
SJ = 195, "SJ": 195,
SB = 188, "SB": 188,
NF = 158, "NF": 158,
RS = 239, "RS": 239,
DE = 56, "DE": 56,
EH = 65, "EH": 65,
EE = 63, "EE": 63,
SD = 190, "SD": 190,
ML = 140, "ML": 140,
TC = 206, "TC": 206,
MZ = 154, "MZ": 154,
BS = 32, "BS": 32,
UY = 226, "UY": 226,
SI = 194, "SI": 194,
AI = 7 "AI": 7
}; };
const keys = Object.keys(CountryCodes); // Get id of a country from a 2 char code
const values = Object.values(CountryCodes); export function getCountryID(code:string) : number {
const upperCode = code.toUpperCase();
export default function getCountryID(code:string) : number { if (upperCode in countryCodes) {
// Get id of a country from a 2 char code return countryCodes[upperCode];
const upperCode:string = code.toUpperCase();
if (upperCode in CountryCodes) {
const code = values[keys.indexOf(upperCode)];
if (typeof(code) === "string") {
return 0;
}
return code;
} }
return 0; return 0;

View file

@ -1,6 +1,6 @@
import { ConsoleHelper } from "../ConsoleHelper"; import { ConsoleHelper } from "../ConsoleHelper";
import fetch from "node-fetch"; import fetch from "node-fetch";
import getCountryID from "./Country"; import { getCountryID } from "./Country";
import { generateSession } from "./Util"; import { generateSession } from "./Util";
import LatLng from "./objects/LatLng"; import LatLng from "./objects/LatLng";
import LoginInfo from "./objects/LoginInfo"; import LoginInfo from "./objects/LoginInfo";
@ -14,6 +14,7 @@ import Shared from "./objects/Shared";
import osu from "../osuTyping"; import osu from "../osuTyping";
import IpZxqResponse from "./interfaces/IpZxqResponse"; import IpZxqResponse from "./interfaces/IpZxqResponse";
import { IncomingMessage, ServerResponse } from "http"; import { IncomingMessage, ServerResponse } from "http";
import UserInfo from "./objects/database/UserInfo";
const { decrypt: aesDecrypt } = require("aes256"); const { decrypt: aesDecrypt } = require("aes256");
const incorrectLoginResponse:Buffer = osu.Bancho.Writer().LoginReply(-1).toBuffer; const incorrectLoginResponse:Buffer = osu.Bancho.Writer().LoginReply(-1).toBuffer;
@ -36,7 +37,7 @@ enum LoginResult {
function TestLogin(loginInfo:LoginInfo, shared:Shared) { function TestLogin(loginInfo:LoginInfo, shared:Shared) {
return new Promise<LoginResult>(async (resolve, reject) => { return new Promise<LoginResult>(async (resolve, reject) => {
const userDBData:any = await shared.database.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]); const userDBData = await shared.userInfoRepository.selectByUsername(loginInfo.username);
// Make sure a user was found in the database // Make sure a user was found in the database
if (userDBData == null) return resolve(LoginResult.INCORRECT); if (userDBData == null) return resolve(LoginResult.INCORRECT);
@ -81,7 +82,7 @@ export default async function LoginProcess(req:IncomingMessage, res:ServerRespon
} }
const loginResult:LoginResult = await TestLogin(loginInfo, shared); const loginResult:LoginResult = await TestLogin(loginInfo, shared);
let osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
let newUser:User | undefined; let newUser:User | undefined;
let friendsPresence:Buffer = Buffer.alloc(0); let friendsPresence:Buffer = Buffer.alloc(0);
@ -122,7 +123,10 @@ export default async function LoginProcess(req:IncomingMessage, res:ServerRespon
} }
// Get information about the user from the database // Get information about the user from the database
const userDB = await shared.database.query("SELECT id FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]); const userInfo = await shared.userInfoRepository.selectByUsername(loginInfo.username);
if (userInfo == null) {
return;
}
// Create a token for the client // Create a token for the client
const newClientToken:string = await generateSession(); const newClientToken:string = await generateSession();
@ -135,7 +139,7 @@ export default async function LoginProcess(req:IncomingMessage, res:ServerRespon
} }
// Retreive the newly created user // Retreive the newly created user
newUser = shared.users.add(newClientToken, new User(userDB.id, loginInfo.username, newClientToken, shared)); newUser = shared.users.add(newClientToken, new User(userInfo.id, loginInfo.username, newClientToken, userInfo.tags, shared));
// Set tourney client flag // Set tourney client flag
newUser.isTourneyUser = isTourneyClient; newUser.isTourneyUser = isTourneyClient;
newUser.location = userLocation; newUser.location = userLocation;
@ -169,24 +173,31 @@ export default async function LoginProcess(req:IncomingMessage, res:ServerRespon
shared.chatManager.SendChannelListing(newUser); shared.chatManager.SendChannelListing(newUser);
// Construct & send user's friends list // Construct & send user's friends list
const userFriends = await shared.database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]); const friends = await shared.database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]);
const friendsArray:Array<number> = new Array<number>(); const friendsArray:Array<number> = new Array<number>();
for (let useFriend of userFriends) { for (const friend of friends) {
const friendId:number = useFriend.friendsWith; const friendId:number = friend.friendsWith;
friendsArray.push(friendId); friendsArray.push(friendId);
// Also fetch presence for friend if they are online // Also fetch presence for friend if they are online
if (shared.users.getById(friendId) === undefined) { continue; } if (shared.users.getById(friendId) === undefined) { continue; }
const friendPresence = UserPresence(shared, friendId); const friendPresence = UserPresence(shared, friendId);
if (friendPresence === undefined) { continue; } if (friendPresence === undefined) {
continue;
}
friendsPresence = Buffer.concat([ friendsPresence = Buffer.concat([
friendsPresence, friendsPresence,
friendPresence friendPresence
], friendsPresence.length + friendPresence.length); ], friendsPresence.length + friendPresence.length);
} }
osuPacketWriter.FriendsList(friendsArray); // Write this to the user's queue rather than just sending it back so we
// don't get the weird `Loading..., Loading...` etc on friends after login.
const friendsPacketWriter = osu.Bancho.Writer();
friendsPacketWriter.FriendsList(friendsArray);
const friendData = friendsPacketWriter.toBuffer;
newUser.addActionToQueue(Buffer.concat([friendData, friendsPresence], friendData.length + friendsPresence.length));
// After sending the user their friends list send them the online users // After sending the user their friends list send them the online users
UserPresenceBundle(newUser); UserPresenceBundle(newUser);
@ -203,7 +214,7 @@ export default async function LoginProcess(req:IncomingMessage, res:ServerRespon
const writerBuffer:Buffer = osuPacketWriter.toBuffer; const writerBuffer:Buffer = osuPacketWriter.toBuffer;
if (newUser === undefined) { if (newUser === undefined) {
res.writeHead(200, { res.writeHead(200, {
"cho-token": "no", "cho-token": "no", // NOTE: You have to specify a token even if it's an incorrect login for some reason.
"Connection": "keep-alive", "Connection": "keep-alive",
"Keep-Alive": "timeout=5, max=100" "Keep-Alive": "timeout=5, max=100"
}); });

View file

@ -8,8 +8,8 @@ import StatusUpdate from "./packets/StatusUpdate";
import UserPresence from "./packets/UserPresence"; import UserPresence from "./packets/UserPresence";
import UserPresenceBundle from "./packets/UserPresenceBundle"; import UserPresenceBundle from "./packets/UserPresenceBundle";
import MatchArray from "./objects/MatchArray"; import MatchArray from "./objects/MatchArray";
import MatchJoinData from "./interfaces/MatchJoinData"; import MatchJoinData from "./interfaces/packetTypes/MatchJoinData";
import MatchData from "./interfaces/MatchData"; import MatchData from "./interfaces/packetTypes/MatchData";
import osu from "../osuTyping"; import osu from "../osuTyping";
import TourneyMatchSpecialInfo from "./packets/TourneyMatchSpecialInfo"; import TourneyMatchSpecialInfo from "./packets/TourneyMatchSpecialInfo";
@ -62,7 +62,7 @@ export default class MultiplayerManager {
} }
let matchFull = true; let matchFull = true;
for (let slot of match.slots) { for (const slot of match.slots) {
if (slot.player instanceof User || slot.status === SlotStatus.Locked) { if (slot.player instanceof User || slot.status === SlotStatus.Locked) {
continue; continue;
} }
@ -111,8 +111,8 @@ export default class MultiplayerManager {
const osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
let bufferToSend = UserPresenceBundle(this.shared); let bufferToSend = UserPresenceBundle(this.shared);
for (let match of this.matches.getIterableItems()) { for (const match of this.matches.getIterableItems()) {
for (let slot of match.slots) { for (const slot of match.slots) {
if (!(slot.player instanceof User) || slot.status === SlotStatus.Locked) { if (!(slot.player instanceof User) || slot.status === SlotStatus.Locked) {
continue; continue;
} }

View file

@ -2,6 +2,7 @@ import DataStream from "./objects/DataStream";
import Shared from "./objects/Shared"; import Shared from "./objects/Shared";
import User from "./objects/User"; import User from "./objects/User";
import osu from "../osuTyping"; import osu from "../osuTyping";
import SpectateFramesData from "./interfaces/packetTypes/SpectateFramesData";
export default class SpectatorManager { export default class SpectatorManager {
private shared:Shared; private shared:Shared;
@ -41,14 +42,13 @@ export default class SpectatorManager {
spectateStream.Send(osuPacketWriter.toBuffer); spectateStream.Send(osuPacketWriter.toBuffer);
} }
// TODO: Interface for spectateFrameData public spectatorFrames(user:User, spectateFramesData:SpectateFramesData) {
public spectatorFrames(user:User, spectateFrameData:any) {
if (user.spectatorStream === undefined) { if (user.spectatorStream === undefined) {
return; return;
} }
const osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
osuPacketWriter.SpectateFrames(spectateFrameData); osuPacketWriter.SpectateFrames(spectateFramesData);
user.spectatorStream.Send(osuPacketWriter.toBuffer); user.spectatorStream.Send(osuPacketWriter.toBuffer);
} }

View file

@ -33,3 +33,7 @@ export function isNullOrEmpty(str:string | undefined | null) {
return false; return false;
} }
export function enumHasFlag(value:number, flag:number) : boolean {
return (value & flag) === flag;
}

35
server/commands/Admin.ts Normal file
View file

@ -0,0 +1,35 @@
import { enumHasFlag } from "../Util";
import { Permissions } from "../enums/Permissions";
import Channel from "../objects/Channel";
import User from "../objects/User";
import BaseCommand from "./BaseCommand";
export default class AdminCommand extends BaseCommand {
public readonly adminOnly:boolean = true;
public readonly helpDescription:string = "Locks/Unlocks a channel and limits conversation to mods and above.";
public exec(channel:Channel, sender:User, args:Array<string>) {
if (!enumHasFlag(sender.permissions, Permissions.Admin) || !enumHasFlag(sender.permissions, Permissions.Peppy)) {
channel.SendBotMessage("You don't have permission to execute that command.");
return;
}
const subCommand = args[0].toLowerCase();
args.shift();
switch (subCommand) {
case "lock":
return adminLock(channel);
}
}
}
function adminLock(channel:Channel) {
if (channel.isSpecial) {
channel.SendBotMessage("Multiplayer channels cannot be locked");
return;
}
channel.isLocked = !channel.isLocked;
channel.SendBotMessage(`Channel is now ${channel.isLocked ? "locked" : "unlocked"}`);
}

View file

@ -5,6 +5,7 @@ import User from "../objects/User";
export default class BaseCommand implements ICommand { export default class BaseCommand implements ICommand {
public shared:Shared; public shared:Shared;
public readonly adminOnly:boolean = false;
public readonly helpText:string = "No help page was found for that command"; public readonly helpText:string = "No help page was found for that command";
public readonly helpDescription:string = "Command has no description set"; public readonly helpDescription:string = "Command has no description set";
public readonly helpArguments:Array<string> = new Array<string>(); public readonly helpArguments:Array<string> = new Array<string>();
@ -14,6 +15,6 @@ export default class BaseCommand implements ICommand {
} }
public exec(channel:Channel, sender:User, args:Array<string>) { public exec(channel:Channel, sender:User, args:Array<string>) {
channel.SendBotMessage(`Sorry ${sender.username}! This command has no functionality yet. Args: ["${args.join('", "')}"]`);
} }
} }

View file

@ -23,7 +23,7 @@ export default class HelpCommand extends BaseCommand {
// All commands // All commands
if (args.length === 0) { if (args.length === 0) {
let constructedHelp = "Help:\n"; let constructedHelp = "Help:\n";
for (let key of this.commandKeys) { for (const key of this.commandKeys) {
constructedHelp += ` !${key} - ${this.commandList[key].helpDescription}\n`; constructedHelp += ` !${key} - ${this.commandList[key].helpDescription}\n`;
} }
channel.SendBotMessage(constructedHelp.slice(0, constructedHelp.length - 1)); channel.SendBotMessage(constructedHelp.slice(0, constructedHelp.length - 1));

View file

@ -1,17 +0,0 @@
import Channel from "../objects/Channel";
import User from "../objects/User";
import BaseCommand from "./BaseCommand";
export default class LockCommand extends BaseCommand {
public readonly helpDescription:string = "Locks/Unlocks a channel and limits conversation to mods and above.";
public exec(channel:Channel, sender:User, args:Array<string>) {
if (channel.isSpecial) {
channel.SendBotMessage("Multiplayer channels cannot be locked");
return;
}
channel.isLocked = !channel.isLocked;
channel.SendBotMessage(`Channel is now ${channel.isLocked ? "locked" : "unlocked"}`);
}
}

View file

@ -6,8 +6,7 @@ import BaseCommand from "./BaseCommand";
export default class MultiplayerCommands extends BaseCommand { export default class MultiplayerCommands extends BaseCommand {
public readonly helpText:string = `Multiplayer Subcommands: public readonly helpText:string = `Multiplayer Subcommands:
!mp start - Starts a multiplayer match with a delay (optional) !mp start - Starts a multiplayer match with a delay (optional)
!mp abort - Aborts the currently running round / countdown !mp abort - Aborts the currently running round / countdown`;
!mp obr - Toggles Battle Royale mode`;
public readonly helpDescription:string = "Command for use in multiplayer matches."; public readonly helpDescription:string = "Command for use in multiplayer matches.";
public readonly helpArguments:Array<string> = ["subCommand"]; public readonly helpArguments:Array<string> = ["subCommand"];
@ -36,9 +35,6 @@ export default class MultiplayerCommands extends BaseCommand {
case "abort": case "abort":
return mpAbort(channel, sender.match); return mpAbort(channel, sender.match);
case "obr":
return mpOBR(channel, sender.match);
} }
} }
} }
@ -90,7 +86,3 @@ function mpAbort(channel:Channel, match:Match) {
channel.SendBotMessage("Aborted current round"); channel.SendBotMessage("Aborted current round");
} }
} }
function mpOBR(channel:Channel, match:Match) {
}

View file

@ -1,6 +1,6 @@
import Channel from "../objects/Channel"; import Channel from "../objects/Channel";
import User from "../objects/User"; import User from "../objects/User";
import { RankingModes } from "../enums/RankingModes"; import { RankingMode } from "../enums/RankingMode";
import BaseCommand from "./BaseCommand"; import BaseCommand from "./BaseCommand";
export default class RankingCommand extends BaseCommand { export default class RankingCommand extends BaseCommand {
@ -18,15 +18,15 @@ export default class RankingCommand extends BaseCommand {
switch (args[0].toLowerCase()) { switch (args[0].toLowerCase()) {
case "pp": case "pp":
sender.rankingMode = RankingModes.PP; sender.rankingMode = RankingMode.PP;
channel.SendBotMessage("Set ranking mode to pp."); channel.SendBotMessage("Set ranking mode to pp.");
break; break;
case "score": case "score":
sender.rankingMode = RankingModes.RANKED_SCORE; sender.rankingMode = RankingMode.RANKED_SCORE;
channel.SendBotMessage("Set ranking mode to score."); channel.SendBotMessage("Set ranking mode to score.");
break; break;
case "acc": case "acc":
sender.rankingMode = RankingModes.AVG_ACCURACY; sender.rankingMode = RankingMode.AVG_ACCURACY;
channel.SendBotMessage("Set ranking mode to accuracy."); channel.SendBotMessage("Set ranking mode to accuracy.");
break; break;
} }

7
server/enums/Mode.ts Normal file
View file

@ -0,0 +1,7 @@
export enum Mode {
Unknown = -1,
Osu,
Taiko,
Catch,
Mania
}

View file

@ -1,4 +1,33 @@
// TODO: Mods enum // TODO: Complete mods enum.
export enum Mods { export enum Mods {
None None,
NoFail = 1 << 0,
Easy = 1 << 1,
// 2 was used for the "No Video" mod but that's gone now.
Hidden = 1 << 3,
HardRock = 1 << 4,
SuddenDeath = 1 << 5,
DoubleTime = 1 << 6,
Relax = 1 << 7,
HalfTime = 1 << 8,
Nightcore = 1 << 9,
Flashlight = 1 << 10,
Autoplay = 1 << 11,
SpunOut = 1 << 12,
Autopilot = 1 << 13, // I think this is autopilot???
Perfect = 1 << 14,
Mania4K = 1 << 15,
Mania5K = 1 << 16,
Mania6K = 1 << 17,
Mania7K = 1 << 18,
Mania8K = 1 << 19,
FadeIn = 1 << 20,
Random = 1 << 21,
Cinema = 1 << 22,
Target = 1 << 23,
Mania9K = 1 << 24,
ManiaCoop = 1 << 25,
Mania1K = 1 << 26,
Mania3K = 1 << 27,
Mania2K = 1 << 28
} }

View file

@ -1,4 +1,5 @@
export enum Permissions { export enum Permissions {
None = 0,
BAT = 2, BAT = 2,
Supporter = 4, Supporter = 4,
Friend = 8, Friend = 8,

View file

@ -1,5 +1,5 @@
export enum RankingModes { export enum RankingMode {
PP, PP,
RANKED_SCORE, RANKED_SCORE,
AVG_ACCURACY AVG_ACCURACY
}; }

View file

@ -4,6 +4,7 @@ import User from "../objects/User";
export default interface ICommand { export default interface ICommand {
shared:Shared, shared:Shared,
adminOnly:boolean,
helpText:string, helpText:string,
helpDescription:string, helpDescription:string,
exec: (channel:Channel, sender:User, args:Array<string>) => void exec: (channel:Channel, sender:User, args:Array<string>) => void

View file

@ -1,19 +0,0 @@
export default interface MatchScoreData {
time:number,
id:number,
count300:number,
count100:number,
count50:number,
countGeki:number,
countKatu:number,
countMiss:number,
totalScore:number,
maxCombo:number,
currentCombo:number,
perfect:boolean,
currentHp:number,
tagByte:number,
usingScoreV2:boolean,
comboPortion:number,
bonusPortion:number
}

View file

@ -1,5 +1,11 @@
import MatchData from "./MatchData" import ChannelData from "./packetTypes/ChannelData"
import MessageData from "./MessageData" import MatchData from "./packetTypes/MatchData"
import MessageData from "./packetTypes/MessageData"
import ScoreFrameData from "./packetTypes/ScoreFrameData"
import SpectateFramesData from "./packetTypes/SpectateFramesData"
import StatusUpdateData from "./packetTypes/StatusUpdateData"
import UserPresenceData from "./packetTypes/UserPresenceData"
import UserQuitData from "./packetTypes/UserQuitData"
export default interface OsuPacketWriter { export default interface OsuPacketWriter {
// Functions // Functions
@ -7,59 +13,59 @@ export default interface OsuPacketWriter {
CommandError() : OsuPacketWriter, CommandError() : OsuPacketWriter,
SendMessage(data:MessageData) : OsuPacketWriter, SendMessage(data:MessageData) : OsuPacketWriter,
Ping() : OsuPacketWriter, Ping() : OsuPacketWriter,
HandleIrcChangeUsername(data:any) : OsuPacketWriter, HandleIrcChangeUsername(data:string) : OsuPacketWriter,
HandleIrcQuit() : OsuPacketWriter, HandleIrcQuit() : OsuPacketWriter,
HandleOsuUpdate(data:any) : OsuPacketWriter, HandleOsuUpdate(data:StatusUpdateData) : OsuPacketWriter,
HandleUserQuit(data:any) : OsuPacketWriter, HandleUserQuit(data:UserQuitData) : OsuPacketWriter,
SpectatorJoined(data:any) : OsuPacketWriter, SpectatorJoined(data:number) : OsuPacketWriter,
SpectatorLeft(data:any) : OsuPacketWriter, SpectatorLeft(data:number) : OsuPacketWriter,
SpectateFrames(data:any) : OsuPacketWriter, SpectateFrames(data:SpectateFramesData) : OsuPacketWriter,
VersionUpdate() : OsuPacketWriter, VersionUpdate() : OsuPacketWriter,
SpectatorCantSpectate(data:any) : OsuPacketWriter, SpectatorCantSpectate(data:number) : OsuPacketWriter,
GetAttention() : OsuPacketWriter, GetAttention() : OsuPacketWriter,
Announce(data:string) : OsuPacketWriter, Announce(data:string) : OsuPacketWriter,
MatchUpdate(data:MatchData) : OsuPacketWriter, MatchUpdate(data:MatchData) : OsuPacketWriter,
MatchNew(data:any) : OsuPacketWriter, MatchNew(data:MatchData) : OsuPacketWriter,
MatchDisband(data:any) : OsuPacketWriter, MatchDisband(data:number) : OsuPacketWriter,
MatchJoinSuccess(data:any) : OsuPacketWriter, MatchJoinSuccess(data:MatchData) : OsuPacketWriter,
MatchJoinFail() : OsuPacketWriter, MatchJoinFail() : OsuPacketWriter,
FellowSpectatorJoined(data:any) : OsuPacketWriter, FellowSpectatorJoined(data:number) : OsuPacketWriter,
FellowSpectatorLeft(data:any) : OsuPacketWriter, FellowSpectatorLeft(data:number) : OsuPacketWriter,
MatchStart(data:any) : OsuPacketWriter, MatchStart(data:MatchData) : OsuPacketWriter,
MatchScoreUpdate(data:any) : OsuPacketWriter, MatchScoreUpdate(data:ScoreFrameData) : OsuPacketWriter,
MatchTransferHost(data:any) : OsuPacketWriter, MatchTransferHost() : OsuPacketWriter,
MatchAllPlayersLoaded() : OsuPacketWriter, MatchAllPlayersLoaded() : OsuPacketWriter,
MatchPlayerFailed(data:any) : OsuPacketWriter, MatchPlayerFailed(data:number) : OsuPacketWriter,
MatchComplete() : OsuPacketWriter, MatchComplete() : OsuPacketWriter,
MatchSkip() : OsuPacketWriter, MatchSkip() : OsuPacketWriter,
Unauthorised() : OsuPacketWriter, Unauthorised() : OsuPacketWriter,
ChannelJoinSuccess(data:any) : OsuPacketWriter, ChannelJoinSuccess(data:string) : OsuPacketWriter,
ChannelAvailable(data:any) : OsuPacketWriter, ChannelAvailable(data:ChannelData) : OsuPacketWriter,
ChannelRevoked(data:any) : OsuPacketWriter, ChannelRevoked(data:string) : OsuPacketWriter,
ChannelAvailableAutojoin(data:any) : OsuPacketWriter, ChannelAvailableAutojoin(data:ChannelData) : OsuPacketWriter,
BeatmapInfoReply() : OsuPacketWriter, BeatmapInfoReply() : OsuPacketWriter,
LoginPermissions(data:number) : OsuPacketWriter, LoginPermissions(data:number) : OsuPacketWriter,
FriendsList(data:Array<number>) : OsuPacketWriter, FriendsList(data:Array<number>) : OsuPacketWriter,
ProtocolNegotiation(data:number) : OsuPacketWriter, ProtocolNegotiation(data:number) : OsuPacketWriter,
TitleUpdate(data:string) : OsuPacketWriter, TitleUpdate(data:string) : OsuPacketWriter,
Monitor() : OsuPacketWriter, Monitor() : OsuPacketWriter,
MatchPlayerSkipped(data:any) : OsuPacketWriter, MatchPlayerSkipped(data:number) : OsuPacketWriter,
UserPresence(data:any) : OsuPacketWriter, UserPresence(data:UserPresenceData) : OsuPacketWriter,
Restart(data:any) : OsuPacketWriter, Restart(data:number) : OsuPacketWriter,
Invite(data:any) : OsuPacketWriter, Invite(data:MessageData) : OsuPacketWriter,
ChannelListingComplete() : OsuPacketWriter, ChannelListingComplete() : OsuPacketWriter,
MatchChangePassword(data:any) : OsuPacketWriter, MatchChangePassword(data:string) : OsuPacketWriter,
BanInfo(data:any) : OsuPacketWriter, BanInfo(data:number) : OsuPacketWriter,
UserSilenced(data:any) : OsuPacketWriter, UserSilenced(data:number) : OsuPacketWriter,
UserPresenceSingle(data:any) : OsuPacketWriter, UserPresenceSingle(data:number) : OsuPacketWriter,
UserPresenceBundle(data:any) : OsuPacketWriter, UserPresenceBundle(data:Array<number>) : OsuPacketWriter,
UserPMBlocked(data:any) : OsuPacketWriter, UserPMBlocked(data:MessageData) : OsuPacketWriter,
TargetIsSilenced(data:any) : OsuPacketWriter, TargetIsSilenced(data:MessageData) : OsuPacketWriter,
VersionUpdateForced() : OsuPacketWriter, VersionUpdateForced() : OsuPacketWriter,
SwitchServer(data:any) : OsuPacketWriter, SwitchServer(data:number) : OsuPacketWriter,
AccountRestricted() : OsuPacketWriter, AccountRestricted() : OsuPacketWriter,
RTX(data:any) : OsuPacketWriter, RTX(data:string) : OsuPacketWriter,
SwitchTourneyServer(data:any) : OsuPacketWriter SwitchTourneyServer(data:string) : OsuPacketWriter
toBuffer : Buffer toBuffer : Buffer
} }

View file

@ -1,12 +1,12 @@
import Slot from "../objects/Slot"; import Slot from "../objects/Slot";
import User from "../objects/User"; import User from "../objects/User";
import MatchScoreData from "./MatchScoreData"; import ScoreFrameData from "./packetTypes/ScoreFrameData";
export default interface PlayerScore { export default interface PlayerScore {
player:User, player: User,
slot:Slot, slot: Slot,
score:number, score: number,
isCurrentlyFailed:boolean, isCurrentlyFailed: boolean,
hasFailed:boolean, hasFailed: boolean,
_raw?:MatchScoreData _raw?: ScoreFrameData
} }

View file

@ -0,0 +1,5 @@
export default interface ChannelData {
channelName: string,
channelTopic: string,
channelUserCount: number
}

View file

@ -0,0 +1,8 @@
export default interface PresenceData {
status: number,
statusText: string,
beatmapId: number,
beatmapChecksum: string,
currentMods: number,
playMode: number,
}

View file

@ -0,0 +1,7 @@
export default interface ReplayFrameData {
buttonState: number,
bt: number,
mouseX: number,
mouseY: number,
time: number
}

View file

@ -0,0 +1,20 @@
export default interface ScoreFrameData {
time: number,
id: number,
count300: number,
count100: number,
count50: number,
countGeki: number,
countKatu: number,
countMiss: number,
totalScore: number,
maxCombo: number,
currentCombo: number,
perfect: boolean,
currentHp: number,
tagByte: number,
usingScoreV2: boolean,
// Only exists if usingScoreV2 = true
comboPortion?: number,
bonusPortion?: number
}

View file

@ -0,0 +1,9 @@
import ReplayFrameData from "./ReplayFrameData";
import ScoreFrameData from "./ScoreFrameData";
export default interface SpectateFramesData {
extra: number,
replayFrames: Array<ReplayFrameData>,
action: number,
scoreFrame: ScoreFrameData
}

View file

@ -0,0 +1,15 @@
export default interface StatusUpdateData {
userId: number,
status: number,
statusText: string,
beatmapChecksum: string,
currentMods: number,
playMode: number,
beatmapId: number,
rankedScore: number,
accuracy: number,
playCount: number,
totalScore: number,
rank: number,
performance: number
}

View file

@ -0,0 +1,12 @@
import { Permissions } from "../../enums/Permissions";
export default interface UserPresenceData {
userId: number,
username: string,
timezone: number,
countryId: number,
permissions: Permissions,
longitude: number,
latitude: number,
rank: number
}

View file

@ -0,0 +1,4 @@
export default interface UserQuitData {
userId: number,
state: number
}

View file

@ -74,7 +74,7 @@ export default class DataStream {
public Send(data:Buffer) { public Send(data:Buffer) {
this.checkInactive(); this.checkInactive();
for (let user of this.users.getIterableItems()) { for (const user of this.users.getIterableItems()) {
user.addActionToQueue(data); user.addActionToQueue(data);
} }
if (Constants.DEBUG) { if (Constants.DEBUG) {
@ -85,7 +85,7 @@ export default class DataStream {
public SendWithExclusion(data:Buffer, exclude:User) { public SendWithExclusion(data:Buffer, exclude:User) {
this.checkInactive(); this.checkInactive();
for (let user of this.users.getIterableItems()) { for (const user of this.users.getIterableItems()) {
if (user.uuid !== exclude.uuid) { if (user.uuid !== exclude.uuid) {
user.addActionToQueue(data); user.addActionToQueue(data);
} }

View file

@ -26,7 +26,7 @@ export default class DataStreamArray extends FunkyArray<DataStream> {
} }
public RemoveUserFromAllStreams(user:User) { public RemoveUserFromAllStreams(user:User) {
for (let stream of this.getIterableItems()) { for (const stream of this.getIterableItems()) {
stream.RemoveUser(user); stream.RemoveUser(user);
} }
} }

View file

@ -1,5 +1,6 @@
import { ConsoleHelper } from "../../ConsoleHelper"; import { ConsoleHelper } from "../../ConsoleHelper";
import { createPool, Pool } from "mysql2"; import { createPool, Pool, RowDataPacket } from "mysql2";
import { DBInDataType } from "../types/DBTypes";
export default class Database { export default class Database {
private connectionPool:Pool; private connectionPool:Pool;
@ -20,50 +21,77 @@ export default class Database {
ConsoleHelper.printInfo(`Connected DB connection pool. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`); ConsoleHelper.printInfo(`Connected DB connection pool. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
} }
public query(query = "", data?:Array<any>) { public execute(query:string, data?:Array<DBInDataType>) {
const limited = query.includes("LIMIT 1"); return new Promise<boolean>((resolve, reject) => {
return new Promise<any>((resolve, reject) => {
this.connectionPool.getConnection((err, connection) => { this.connectionPool.getConnection((err, connection) => {
if (err) { if (err) {
reject(err); return reject(err);
try { }
connection.release();
} catch (e) { if (data == null) {
ConsoleHelper.printError("Failed to release mysql connection\n" + err); connection.execute(query, (err, result) => {
} if (err) {
connection.release();
return reject(err);
}
resolve(result !== undefined);
});
} else {
connection.execute(query, data, (err, result) => {
if (err) {
connection.release();
return reject(err);
}
resolve(result !== undefined);
});
}
});
});
}
public query(query:string, data?:Array<DBInDataType>) {
return new Promise<RowDataPacket[]>((resolve, reject) => {
this.connectionPool.getConnection((err, connection) => {
if (err) {
return reject(err);
} else { } else {
// Use old query // Use old query
if (data == null) { if (data == null) {
connection.query(query, (err, data) => { connection.query<RowDataPacket[]>(query, (err, rows) => {
connection.release();
if (err) { if (err) {
reject(err); return reject(err);
connection.release();
} else {
dataReceived(resolve, data, limited);
connection.release();
} }
resolve(rows);
connection.release();
}); });
} }
// Use new prepared statements w/ placeholders // Use new prepared statements w/ placeholders
else { else {
connection.execute(query, data, (err, data) => { connection.execute<RowDataPacket[]>(query, data, (err, rows) => {
connection.release();
if (err) { if (err) {
reject(err); return reject(err);
connection.release();
} else {
dataReceived(resolve, data, limited);
connection.release();
} }
resolve(rows);
connection.release();
}); });
} }
} }
}); });
}); });
} }
}
function dataReceived(resolveCallback:(value:unknown) => void, data:any, limited:boolean = false) : void { public async querySingle(query:string, data?:Array<DBInDataType>) {
if (limited) resolveCallback(data[0]); const dbData = await this.query(query, data);
else resolveCallback(data); if (dbData != null && dbData.length > 0) {
return dbData[0];
}
return null;
}
} }

View file

@ -1,5 +1,5 @@
export default class FunkyArray<T> { export default class FunkyArray<T> {
private items:any = {}; private items:{ [id: string]: T } = {};
private itemKeys:Array<string> = Object.keys(this.items); private itemKeys:Array<string> = Object.keys(this.items);
private iterableArray:Array<T> = new Array<T>(); private iterableArray:Array<T> = new Array<T>();
@ -29,8 +29,8 @@ export default class FunkyArray<T> {
} }
public regenerateIterableArray() : void { public regenerateIterableArray() : void {
this.iterableArray = new Array(); this.iterableArray = new Array<T>();
for (let itemKey of this.itemKeys) { for (const itemKey of this.itemKeys) {
this.iterableArray.push(this.items[itemKey]); this.iterableArray.push(this.items[itemKey]);
} }
this.itemKeys = Object.keys(this.items); this.itemKeys = Object.keys(this.items);
@ -64,7 +64,7 @@ export default class FunkyArray<T> {
return this.itemKeys; return this.itemKeys;
} }
public getItems() : any { public getItems() : { [id: string]: T } {
return this.items; return this.items;
} }

View file

@ -5,13 +5,19 @@ import Slot from "./Slot";
import User from "./User"; import User from "./User";
import StatusUpdate from "../packets/StatusUpdate"; import StatusUpdate from "../packets/StatusUpdate";
import { SlotStatus } from "../enums/SlotStatus"; import { SlotStatus } from "../enums/SlotStatus";
import MatchData from "../interfaces/MatchData"; import MatchData from "../interfaces/packetTypes/MatchData";
import { Team } from "../enums/Team"; import { Team } from "../enums/Team";
import MatchStartSkipData from "../interfaces/MatchStartSkipData"; import MatchStartSkipData from "../interfaces/packetTypes/MatchStartSkipData";
import { Mods } from "../enums/Mods"; import { Mods } from "../enums/Mods";
import PlayerScore from "../interfaces/PlayerScore"; import PlayerScore from "../interfaces/PlayerScore";
import MatchScoreData from "../interfaces/MatchScoreData"; import { enumHasFlag } from "../Util";
import osu from "../../osuTyping"; import osu from "../../osuTyping";
import ScoreFrameData from "../interfaces/packetTypes/ScoreFrameData";
// Mods which need to be applied to the match during freemod.
const matchFreemodGlobalMods:Array<Mods> = [
Mods.DoubleTime, Mods.Nightcore, Mods.HalfTime
]
export default class Match { export default class Match {
// osu! Data // osu! Data
@ -236,7 +242,7 @@ export default class Match {
} }
queryData.push(this.matchId); queryData.push(this.matchId);
await this.shared.database.query( await this.shared.database.execute(
`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, `UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`,
queryData queryData
); );
@ -391,11 +397,16 @@ export default class Match {
allSkipped = false; allSkipped = false;
} }
const slotId = user.matchSlot?.slotId ?? Number.MIN_VALUE;
if (slotId === Number.MIN_VALUE) {
return;
}
// All players have finished playing, finish the match // All players have finished playing, finish the match
if (allSkipped) { if (allSkipped) {
const osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
osuPacketWriter.MatchPlayerSkipped(user.id); osuPacketWriter.MatchPlayerSkipped(slotId);
osuPacketWriter.MatchSkip(); osuPacketWriter.MatchSkip();
this.matchStream.Send(osuPacketWriter.toBuffer); this.matchStream.Send(osuPacketWriter.toBuffer);
@ -404,7 +415,7 @@ export default class Match {
} else { } else {
const osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
osuPacketWriter.MatchPlayerSkipped(user.id); osuPacketWriter.MatchPlayerSkipped(slotId);
this.matchStream.Send(osuPacketWriter.toBuffer); this.matchStream.Send(osuPacketWriter.toBuffer);
} }
@ -420,7 +431,6 @@ export default class Match {
} }
} }
// TODO: Fix not being able to add DT when freemod is active
public updateMods(user:User, mods:Mods) { public updateMods(user:User, mods:Mods) {
const slot = user.matchSlot; const slot = user.matchSlot;
if (!(slot instanceof Slot)) { if (!(slot instanceof Slot)) {
@ -431,6 +441,19 @@ export default class Match {
if (this.specialModes === 1) { if (this.specialModes === 1) {
slot.mods = mods; slot.mods = mods;
// Extra check for host during freemod
if (User.Equals(this.host, user)) {
let generatedMatchModList = 0;
for (const mod of matchFreemodGlobalMods) {
if (enumHasFlag(slot.mods, mod)) {
slot.mods -= mod;
generatedMatchModList += mod;
}
}
this.activeMods = generatedMatchModList;
}
this.sendMatchUpdate(); this.sendMatchUpdate();
} else { } else {
if (!User.Equals(this.host, user)) { if (!User.Equals(this.host, user)) {
@ -503,7 +526,7 @@ export default class Match {
// All players have loaded the beatmap, start playing. // All players have loaded the beatmap, start playing.
if (allLoaded) { if (allLoaded) {
let osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
osuPacketWriter.MatchAllPlayersLoaded(); osuPacketWriter.MatchAllPlayersLoaded();
this.matchStream.Send(osuPacketWriter.toBuffer); this.matchStream.Send(osuPacketWriter.toBuffer);
@ -572,9 +595,9 @@ export default class Match {
this.matchLoadSlots = undefined; this.matchLoadSlots = undefined;
this.inProgress = false; this.inProgress = false;
let osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
let queryData:Array<any> = [ const queryData:Array<string | number | null> = [
this.matchId, this.matchId,
this.roundId++, this.roundId++,
this.playMode, this.playMode,
@ -608,7 +631,7 @@ export default class Match {
slot.status = SlotStatus.NotReady; slot.status = SlotStatus.NotReady;
} }
await this.shared.database.query("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData); await this.shared.database.execute("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData);
osuPacketWriter.MatchComplete(); osuPacketWriter.MatchComplete();
@ -625,31 +648,31 @@ export default class Match {
this.playerScores = undefined; this.playerScores = undefined;
} }
updatePlayerScore(user:User, matchScoreData:MatchScoreData) { updatePlayerScore(user:User, scoreFrameData:ScoreFrameData) {
const osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
if (user.matchSlot === undefined || user.matchSlot.player === undefined || this.playerScores === undefined) { if (user.matchSlot === undefined || user.matchSlot.player === undefined || this.playerScores === undefined) {
return; return;
} }
matchScoreData.id = user.id; scoreFrameData.id = user.matchSlot.slotId;
// Update playerScores // Update playerScores
for (const playerScore of this.playerScores) { for (const playerScore of this.playerScores) {
if (playerScore.player?.id === user.id) { if (playerScore.player?.id === user.id) {
playerScore.score = matchScoreData.totalScore; playerScore.score = scoreFrameData.totalScore;
const isCurrentlyFailed = matchScoreData.currentHp == 254; const isCurrentlyFailed = scoreFrameData.currentHp == 254;
playerScore.isCurrentlyFailed = isCurrentlyFailed; playerScore.isCurrentlyFailed = isCurrentlyFailed;
if (!playerScore.hasFailed && isCurrentlyFailed) { if (!playerScore.hasFailed && isCurrentlyFailed) {
playerScore.hasFailed = true; playerScore.hasFailed = true;
} }
playerScore._raw = matchScoreData; playerScore._raw = scoreFrameData;
break; break;
} }
} }
osuPacketWriter.MatchScoreUpdate(matchScoreData); osuPacketWriter.MatchScoreUpdate(scoreFrameData);
// Send the newly updated score to all users in the match // Send the newly updated score to all users in the match
this.matchStream.Send(osuPacketWriter.toBuffer); this.matchStream.Send(osuPacketWriter.toBuffer);

View file

@ -3,7 +3,7 @@ import Match from "./Match";
export default class MatchArray extends FunkyArray<Match> { export default class MatchArray extends FunkyArray<Match> {
public getById(id:number) : Match | undefined { public getById(id:number) : Match | undefined {
for (let match of this.getIterableItems()) { for (const match of this.getIterableItems()) {
if (match.matchId === id) { if (match.matchId === id) {
return match; return match;
} }

View file

@ -10,6 +10,9 @@ import User from "./User";
import LatLng from "./LatLng"; import LatLng from "./LatLng";
import Bot from "../Bot"; import Bot from "../Bot";
import { ConsoleHelper } from "../../ConsoleHelper"; import { ConsoleHelper } from "../../ConsoleHelper";
import UserInfoRepository from "../repos/UserInfoRepository";
import { Permissions } from "../enums/Permissions";
import UserModesInfoRepository from "../repos/UserModesInfoRepository";
export default class Shared { export default class Shared {
public readonly chatManager:ChatManager; public readonly chatManager:ChatManager;
@ -21,6 +24,9 @@ export default class Shared {
public readonly users:UserArray; public readonly users:UserArray;
public readonly bot:Bot; public readonly bot:Bot;
public readonly userInfoRepository:UserInfoRepository;
public readonly userModesInfoRepository:UserModesInfoRepository;
public constructor() { public constructor() {
if (!existsSync("./config.json")) { if (!existsSync("./config.json")) {
ConsoleHelper.printError("Config file missing!"); ConsoleHelper.printError("Config file missing!");
@ -33,7 +39,7 @@ export default class Shared {
// Add the bot user // Add the bot user
this.users = new UserArray(); this.users = new UserArray();
const botUser = this.users.add("bot", new User(3, "SillyBot", "bot", this)); const botUser = this.users.add("bot", new User(3, "SillyBot", "bot", Permissions.None, this));
botUser.location = new LatLng(50, -32); botUser.location = new LatLng(50, -32);
this.bot = new Bot(this, botUser); this.bot = new Bot(this, botUser);
@ -46,5 +52,9 @@ export default class Shared {
this.multiplayerManager = new MultiplayerManager(this); this.multiplayerManager = new MultiplayerManager(this);
this.privateChatManager = new PrivateChatManager(this); this.privateChatManager = new PrivateChatManager(this);
// DB Repos
this.userInfoRepository = new UserInfoRepository(this);
this.userModesInfoRepository = new UserModesInfoRepository(this);
} }
} }

View file

@ -1,21 +1,15 @@
import LatLng from "./LatLng"; import LatLng from "./LatLng";
import { RankingModes } from "../enums/RankingModes"; import { RankingMode } from "../enums/RankingMode";
import Match from "./Match"; import Match from "./Match";
import DataStream from "./DataStream"; import DataStream from "./DataStream";
import StatusUpdate from "../packets/StatusUpdate"; import StatusUpdate from "../packets/StatusUpdate";
import Shared from "../objects/Shared"; import Shared from "../objects/Shared";
import Slot from "./Slot"; import Slot from "./Slot";
import Channel from "./Channel"; import Channel from "./Channel";
import PresenceData from "../interfaces/packetTypes/PresenceData";
const rankingModes = [ import { Permissions } from "../enums/Permissions";
"pp_raw",
"ranked_score",
"avg_accuracy"
];
export default class User { export default class User {
private static readonly EMPTY_BUFFER = Buffer.alloc(0);
public shared:Shared; public shared:Shared;
public id:number; public id:number;
@ -23,12 +17,13 @@ export default class User {
public uuid:string; public uuid:string;
public readonly connectTime:number = Date.now(); public readonly connectTime:number = Date.now();
public timeoutTime:number = Date.now() + 30000; public timeoutTime:number = Date.now() + 30000;
public queue:Buffer = User.EMPTY_BUFFER; public queue:Buffer = Buffer.allocUnsafe(0);
// Binato data // Binato data
public rankingMode:RankingModes = RankingModes.PP; public rankingMode:RankingMode = RankingMode.PP;
public spectatorStream?:DataStream; public spectatorStream?:DataStream;
public spectatingUser?:User; public spectatingUser?:User;
public permissions:Permissions;
// osu! data // osu! data
public playMode:number = 0; public playMode:number = 0;
@ -66,10 +61,11 @@ export default class User {
return user0.uuid === user1.uuid; return user0.uuid === user1.uuid;
} }
public constructor(id:number, username:string, uuid:string, shared:Shared) { public constructor(id:number, username:string, uuid:string, permissions:Permissions, shared:Shared) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.uuid = uuid; this.uuid = uuid;
this.permissions = permissions;
this.shared = shared; this.shared = shared;
} }
@ -80,11 +76,11 @@ export default class User {
} }
clearQueue() { clearQueue() {
this.queue = User.EMPTY_BUFFER; this.queue = Buffer.allocUnsafe(0);
} }
// Updates the user's current action // Updates the user's current action
updatePresence(action:any) { updatePresence(action:PresenceData) {
this.actionID = action.status; this.actionID = action.status;
this.actionText = action.statusText; this.actionText = action.statusText;
this.beatmapChecksum = action.beatmapChecksum; this.beatmapChecksum = action.beatmapChecksum;
@ -99,26 +95,27 @@ export default class User {
// Gets the user's score information from the database and caches it // Gets the user's score information from the database and caches it
async updateUserInfo(forceUpdate:boolean = false) { async updateUserInfo(forceUpdate:boolean = false) {
const userScoreDB = await this.shared.database.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]); const userScoreDB = await this.shared.userModesInfoRepository.selectByUserIdModeId(this.id, this.playMode);
const mappedRankingMode = rankingModes[this.rankingMode]; const userRank = await this.shared.userModesInfoRepository.selectRankByIdModeIdRankingMode(this.id, this.playMode, this.rankingMode);
const userRankDB = await this.shared.database.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);
if (userScoreDB == null || userRankDB == null) throw "fuck"; if (userScoreDB == null || userRank == null) throw "fuck";
this.rank = userRank;
// Handle "if we should update" checks for each rankingMode // Handle "if we should update" checks for each rankingMode
let userScoreUpdate = false; let userScoreUpdate = false;
switch (this.rankingMode) { switch (this.rankingMode) {
case RankingModes.PP: case RankingMode.PP:
if (this.pp != userScoreDB.pp_raw) if (this.pp != userScoreDB.pp_raw)
userScoreUpdate = true; userScoreUpdate = true;
break; break;
case RankingModes.RANKED_SCORE: case RankingMode.RANKED_SCORE:
if (this.rankedScore != userScoreDB.ranked_score) if (this.rankedScore != userScoreDB.ranked_score)
userScoreUpdate = true; userScoreUpdate = true;
break; break;
case RankingModes.AVG_ACCURACY: case RankingMode.AVG_ACCURACY:
if (this.accuracy != userScoreDB.avg_accuracy) if (this.accuracy != userScoreDB.avg_accuracy)
userScoreUpdate = true; userScoreUpdate = true;
break; break;
@ -129,14 +126,6 @@ export default class User {
this.accuracy = userScoreDB.avg_accuracy; this.accuracy = userScoreDB.avg_accuracy;
this.playCount = userScoreDB.playcount; this.playCount = userScoreDB.playcount;
// Fetch rank
for (let i = 0; i < userRankDB.length; i++) {
if (userRankDB[i]["user_id"] == this.id) {
this.rank = i + 1;
break;
}
}
// Set PP to none if ranking mode is not PP // Set PP to none if ranking mode is not PP
if (this.rankingMode == 0) this.pp = userScoreDB.pp_raw; if (this.rankingMode == 0) this.pp = userScoreDB.pp_raw;
else this.pp = 0; else this.pp = 0;

View file

@ -3,7 +3,7 @@ import User from "./User";
export default class UserArray extends FunkyArray<User> { export default class UserArray extends FunkyArray<User> {
public getById(id:number) : User | undefined { public getById(id:number) : User | undefined {
for (let user of this.getIterableItems()) { for (const user of this.getIterableItems()) {
if (user.id == id) if (user.id == id)
return user; return user;
} }
@ -12,7 +12,7 @@ export default class UserArray extends FunkyArray<User> {
} }
public getByUsername(username:string) : User | undefined { public getByUsername(username:string) : User | undefined {
for (let user of this.getIterableItems()) { for (const user of this.getIterableItems()) {
if (user.username === username) if (user.username === username)
return user; return user;
} }

View file

@ -1,3 +1,5 @@
import { Permissions } from "../../enums/Permissions";
export default class UserInfo { export default class UserInfo {
id:number = Number.MIN_VALUE; id:number = Number.MIN_VALUE;
username:string = ""; username:string = "";
@ -10,7 +12,7 @@ export default class UserInfo {
last_login_date:Date = new Date(0); last_login_date:Date = new Date(0);
last_played_mode:number = Number.MIN_VALUE; last_played_mode:number = Number.MIN_VALUE;
online_now:boolean = false; online_now:boolean = false;
tags:number = Number.MIN_VALUE; tags:Permissions = Permissions.None;
supporter:boolean = false; supporter:boolean = false;
web_session:string = ""; web_session:string = "";
verification_needed:boolean = false; verification_needed:boolean = false;

View file

@ -0,0 +1,24 @@
import { Mode } from "../../enums/Mode";
export default class UserModeInfo {
n:number = Number.MIN_VALUE;
user_id:number = Number.MIN_VALUE;
mode_id:Mode = Mode.Unknown;
count300:number = Number.MIN_VALUE;
count100:number = Number.MIN_VALUE;
count50:number = Number.MIN_VALUE;
countmiss:number = Number.MIN_VALUE;
playcount:number = Number.MIN_VALUE;
total_score:number = Number.MIN_VALUE;
ranked_score:number = Number.MIN_VALUE;
pp_rank:number = Number.MIN_VALUE;
pp_raw:number = Number.MIN_VALUE;
count_rank_ss:number = Number.MIN_VALUE;
count_rank_s:number = Number.MIN_VALUE;
count_rank_a:number = Number.MIN_VALUE;
pp_country_rank:number = Number.MIN_VALUE;
playtime:number = Number.MIN_VALUE;
avg_accuracy:number = Number.MIN_VALUE;
level:number = Number.MIN_VALUE;
is_deleted:boolean = false;
}

View file

@ -1,7 +1,7 @@
import User from "../objects/User"; import User from "../objects/User";
export default function AddFriend(user:User, friendId:number) { export default async function AddFriend(user:User, friendId:number) {
user.shared.database.query("INSERT INTO friends (user, friendsWith) VALUES (?, ?)", [ await user.shared.database.execute("INSERT INTO friends (user, friendsWith) VALUES (?, ?)", [
user.id, friendId user.id, friendId
]); ]);
} }

View file

@ -1,7 +1,8 @@
import PresenceData from "../interfaces/packetTypes/PresenceData";
import User from "../objects/User"; import User from "../objects/User";
import StatusUpdate from "./StatusUpdate"; import StatusUpdate from "./StatusUpdate";
export default function ChangeAction(user:User, data:any) { export default function ChangeAction(user:User, data:PresenceData) {
user.updatePresence(data); user.updatePresence(data);
if (user.spectatorStream != null) { if (user.spectatorStream != null) {

View file

@ -13,7 +13,7 @@ export default async function Logout(user:User) {
// Remove user from user list // Remove user from user list
user.shared.users.remove(user.uuid); user.shared.users.remove(user.uuid);
await user.shared.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [user.shared.users.getLength() - 1]); await user.shared.database.execute("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [user.shared.users.getLength() - 1]);
ConsoleHelper.printBancho(`User logged out, took ${Date.now() - logoutStartTime}ms. [User: ${user.username}]`); ConsoleHelper.printBancho(`User logged out, took ${Date.now() - logoutStartTime}ms. [User: ${user.username}]`);
} }

View file

@ -1,4 +1,4 @@
import MessageData from "../interfaces/MessageData"; import MessageData from "../interfaces/packetTypes/MessageData";
import Shared from "../objects/Shared"; import Shared from "../objects/Shared";
import PrivateChannel from "../objects/PrivateChannel"; import PrivateChannel from "../objects/PrivateChannel";
import User from "../objects/User"; import User from "../objects/User";

View file

@ -1,7 +1,7 @@
import User from "../objects/User"; import User from "../objects/User";
export default function RemoveFriend(user:User, friendId:number) { export default async function RemoveFriend(user:User, friendId:number) {
user.shared.database.query("DELETE FROM friends WHERE user = ? AND friendsWith = ? LIMIT 1", [ await user.shared.database.execute("DELETE FROM friends WHERE user = ? AND friendsWith = ? LIMIT 1", [
user.id, friendId user.id, friendId
]); ]);
} }

View file

@ -0,0 +1,10 @@
import { Channel } from "diagnostics_channel";
import MessageData from "../interfaces/packetTypes/MessageData";
import User from "../objects/User";
export default function SendPublicMessage(user:User, message:MessageData) {
const channel = user.shared.chatManager.GetChannelByName(message.target);
if (channel instanceof Channel) {
channel.SendMessage(user, message.message);
}
}

View file

@ -1,10 +1,13 @@
import Shared from "../objects/Shared"; import Shared from "../objects/Shared";
import { RankingModes } from "../enums/RankingModes"; import { RankingMode } from "../enums/RankingMode";
import User from "../objects/User"; import User from "../objects/User";
import osu from "../../osuTyping"; import osu from "../../osuTyping";
export default function StatusUpdate(arg0:User | Shared, id:number) { export default function StatusUpdate(arg0:User | Shared, id:number) {
if (id == 3) return; // Ignore Bot // Ignore Bot
if (id == 3) {
return Buffer.allocUnsafe(0);
}
// Create new osu packet writer // Create new osu packet writer
const osuPacketWriter = osu.Bancho.Writer(); const osuPacketWriter = osu.Bancho.Writer();
@ -18,9 +21,11 @@ export default function StatusUpdate(arg0:User | Shared, id:number) {
// Get user's class // Get user's class
const userData = shared.users.getById(id); const userData = shared.users.getById(id);
if (userData == null) return; if (userData == null) {
return Buffer.allocUnsafe(0);
}
let UserStatusObject = { osuPacketWriter.HandleOsuUpdate({
userId: userData.id, userId: userData.id,
status: userData.actionID, status: userData.actionID,
statusText: userData.actionText, statusText: userData.actionText,
@ -33,10 +38,8 @@ export default function StatusUpdate(arg0:User | Shared, id:number) {
playCount: userData.playCount, playCount: userData.playCount,
totalScore: userData.totalScore, totalScore: userData.totalScore,
rank: userData.rank, rank: userData.rank,
performance: (userData.rankingMode == RankingModes.PP ? userData.pp : 0) performance: (userData.rankingMode == RankingMode.PP ? userData.pp : 0)
}; });
osuPacketWriter.HandleOsuUpdate(UserStatusObject);
// Send data to user's queue // Send data to user's queue
if (arg0 instanceof User) { if (arg0 instanceof User) {

View file

@ -11,9 +11,9 @@ export default function UserPresenceBundle(arg0:User | Shared) : Buffer {
shared = arg0; shared = arg0;
} }
let userIds:Array<number> = new Array<number>(); const userIds:Array<number> = new Array<number>();
for (let userData of shared.users.getIterableItems()) { for (const userData of shared.users.getIterableItems()) {
userIds.push(userData.id); userIds.push(userData.id);
} }

View file

@ -6,7 +6,7 @@ import UserPresenceBundle from "./UserPresenceBundle";
export default function UserStatsRequest(user:User, data:Array<number>) { export default function UserStatsRequest(user:User, data:Array<number>) {
UserPresenceBundle(user); UserPresenceBundle(user);
for (let id of data) { for (const id of data) {
UserPresence(user, id); UserPresence(user, id);
StatusUpdate(user, id); StatusUpdate(user, id);
} }

View file

@ -0,0 +1,59 @@
import { RowDataPacket } from "mysql2";
import Database from "../objects/Database";
import Shared from "../objects/Shared";
import UserInfo from "../objects/database/UserInfo";
export default class UserInfoRepository {
private database:Database;
public constructor(shared:Shared) {
this.database = shared.database;
}
public async selectById(id:number) {
const query = await this.database.query("CALL SelectUserInfoById(?)", [id]);
if (query != null) {
const userInfo = new UserInfo();
populateUserInfoFromRowDataPacket(userInfo, query[0][0]);
return userInfo;
}
return null;
}
public async selectByUsername(username:string) {
const query = await this.database.query("CALL SelectUserInfoByUsername(?)", [username]);
if (query != null) {
const userInfo = new UserInfo();
populateUserInfoFromRowDataPacket(userInfo, query[0][0]);
return userInfo;
}
return null;
}
}
function populateUserInfoFromRowDataPacket(userInfo:UserInfo, rowDataPacket:RowDataPacket) {
userInfo.id = rowDataPacket["id"];
userInfo.username = rowDataPacket["username"];
userInfo.username_safe = rowDataPacket["username_safe"];
userInfo.password_hash = rowDataPacket["password_hash"];
userInfo.password_salt = rowDataPacket["password_salt"];
userInfo.email = rowDataPacket["email"];
userInfo.country = rowDataPacket["country"];
userInfo.reg_date = rowDataPacket["reg_date"];
userInfo.last_login_date = rowDataPacket["last_login_date"];
userInfo.last_played_mode = rowDataPacket["last_played_mode"];
userInfo.online_now = rowDataPacket["online_now"];
userInfo.tags = rowDataPacket["tags"];
userInfo.supporter = rowDataPacket["supporter"];
userInfo.web_session = rowDataPacket["web_session"];
userInfo.verification_needed = rowDataPacket["verification_needed"];
userInfo.password_change_required = rowDataPacket["password_change_required"];
userInfo.has_old_password = rowDataPacket["has_old_password"];
userInfo.password_reset_key = rowDataPacket["password_reset_key"];
userInfo.away_message = rowDataPacket["away_message"];
userInfo.last_modified_time = rowDataPacket["last_modified_time"];
userInfo.is_deleted = rowDataPacket["is_deleted"];
}

View file

@ -0,0 +1,72 @@
import { RowDataPacket } from "mysql2";
import Database from "../objects/Database";
import Shared from "../objects/Shared";
import UserModeInfo from "../objects/database/UserModeInfo";
import { Mode } from "fs";
import { RankingMode } from "../enums/RankingMode";
export default class UserModesInfoRepository {
private database:Database;
public constructor(shared:Shared) {
this.database = shared.database;
}
public async selectByUserIdModeId(id:number, mode:Mode) {
const query = await this.database.query("CALL SelectUserModesInfoByUserIdModeId(?,?)", [id, mode]);
if (query != null) {
const userModeInfo = new UserModeInfo();
populateUserModeInfoFromRowDataPacket(userModeInfo, query[0][0]);
return userModeInfo;
}
return null;
}
public async selectRankByIdModeIdRankingMode(id:number, mode:Mode, rankingMode:RankingMode) : Promise<number | null> {
let query:RowDataPacket[] | undefined;
switch (rankingMode) {
case RankingMode.RANKED_SCORE:
query = await this.database.query("CALL SelectUserScoreRankByIdModeId(?,?)", [id, mode]);
break;
case RankingMode.AVG_ACCURACY:
query = await this.database.query("CALL SelectUserAccRankByIdModeId(?,?)", [id, mode]);
break;
case RankingMode.PP:
default:
query = await this.database.query("CALL SelectUserPPRankByIdModeId(?,?)", [id, mode]);
break;
}
if (query != null && query.length != 0) {
return query[0][0].rank;
}
return null;
}
}
function populateUserModeInfoFromRowDataPacket(userModeInfo:UserModeInfo, rowDataPacket:RowDataPacket) {
userModeInfo.n = rowDataPacket["n"];
userModeInfo.user_id = rowDataPacket["user_id"];
userModeInfo.mode_id = rowDataPacket["mode_id"];
userModeInfo.count300 = rowDataPacket["count300"];
userModeInfo.count100 = rowDataPacket["count100"];
userModeInfo.count50 = rowDataPacket["count50"];
userModeInfo.countmiss = rowDataPacket["countmiss"];
userModeInfo.playcount = rowDataPacket["playcount"];
userModeInfo.total_score = rowDataPacket["total_score"];
userModeInfo.ranked_score = rowDataPacket["ranked_score"];
userModeInfo.pp_rank = rowDataPacket["pp_rank"];
userModeInfo.pp_raw = rowDataPacket["pp_raw"];
userModeInfo.count_rank_ss = rowDataPacket["count_rank_ss"];
userModeInfo.count_rank_s = rowDataPacket["count_rank_s"];
userModeInfo.count_rank_a = rowDataPacket["count_rank_a"];
userModeInfo.pp_country_rank = rowDataPacket["pp_country_rank"];
userModeInfo.playtime = rowDataPacket["playtime"];
userModeInfo.avg_accuracy = rowDataPacket["avg_accuracy"];
userModeInfo.level = rowDataPacket["level"];
userModeInfo.is_deleted = rowDataPacket["is_deleted"];
}

1
server/types/DBTypes.ts Normal file
View file

@ -0,0 +1 @@
export type DBInDataType = string | number | null | undefined;

View file

@ -1,12 +1,11 @@
import { readdirSync, rmSync, renameSync } from "fs"; import { readdirSync, rmSync, readFileSync } from "fs";
const libFiles = readdirSync("./build"); const libFiles = readdirSync("./build");
const mangled = readFileSync("./build/.MANGLED").toString() === "false";
for (const file of libFiles) { for (const file of libFiles) {
if (!file.startsWith("Binato.min.js")) { if (!file.startsWith(mangled ? "Binato.min.js" : "Binato.js")) {
rmSync(`./build/${file}`, { recursive: true }); rmSync(`./build/${file}`, { recursive: true });
} }
} }
//renameSync("./build/combined.js", "./build/index.js");
//renameSync("./build/combined.d.ts", "./build/index.d.ts");

View file

@ -5,6 +5,7 @@
import { readdirSync, lstatSync, readFileSync, writeFileSync } from "fs"; import { readdirSync, lstatSync, readFileSync, writeFileSync } from "fs";
let tsFileData:Array<string> = new Array<string>(); let tsFileData:Array<string> = new Array<string>();
const tsHighPriorityData:Array<string> = new Array<string>();
const tsEvenFirsterData:Array<string> = new Array<string>(); const tsEvenFirsterData:Array<string> = new Array<string>();
const tsVeryFirstData:Array<string> = new Array<string>(); const tsVeryFirstData:Array<string> = new Array<string>();
const tsFirstFileData:Array<string> = new Array<string>(); const tsFirstFileData:Array<string> = new Array<string>();
@ -25,9 +26,11 @@ function readDir(nam:string) {
} else if (file.endsWith(".ts")) { } else if (file.endsWith(".ts")) {
if (file == "Binato.ts") { if (file == "Binato.ts") {
tsLastFileData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString()); tsLastFileData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString());
} else if (file.includes("BaseCommand")) {
tsHighPriorityData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString());
} else if (nam.includes("commands") || file.includes("ConsoleHelper")) { } else if (nam.includes("commands") || file.includes("ConsoleHelper")) {
tsEvenFirsterData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString()); tsEvenFirsterData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString());
} else if (file.includes("FunkyArray") || file.includes("ChatManager") || file.includes("MultiplayerManager") || file === "Bot.ts") { } else if (file.includes("FunkyArray") || file.includes("ChatManager") || file.includes("MultiplayerManager") || file === "BaseCommand.ts" || file.includes("Bot.ts")) {
tsVeryFirstData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString()); tsVeryFirstData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString());
} else if (nam.includes("enum") || nam.includes("packets") || (nam.includes("objects") && !file.includes("FunkyArray") ) || file.includes("SpectatorManager")) { } else if (nam.includes("enum") || nam.includes("packets") || (nam.includes("objects") && !file.includes("FunkyArray") ) || file.includes("SpectatorManager")) {
tsFirstFileData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString()); tsFirstFileData.push(readFileSync((`${nam}/${file}`).replace("//", "/")).toString());
@ -40,7 +43,7 @@ function readDir(nam:string) {
readDir("./"); readDir("./");
tsFileData = tsFileData.concat(tsEvenFirsterData).concat(tsVeryFirstData).concat(tsFirstFileData).concat(tsEverythingElse).concat(tsLastFileData); tsFileData = tsFileData.concat(tsHighPriorityData).concat(tsEvenFirsterData).concat(tsVeryFirstData).concat(tsFirstFileData).concat(tsEverythingElse).concat(tsLastFileData);
const combinedFiles = tsFileData.join("\n"); const combinedFiles = tsFileData.join("\n");
@ -53,7 +56,7 @@ import { Registry, collectDefaultMetrics } from "prom-client";
import { RedisClientType, createClient } from "redis"; import { RedisClientType, createClient } from "redis";
import { readFileSync, existsSync } from "fs"; import { readFileSync, existsSync } from "fs";
import { randomBytes, pbkdf2 } from "crypto"; import { randomBytes, pbkdf2 } from "crypto";
import { createPool, Pool } from "mysql2"; import { createPool, Pool, RowDataPacket } from "mysql2";
import * as dyetty from "dyetty"; import * as dyetty from "dyetty";
import fetch from "node-fetch"; import fetch from "node-fetch";
import http from "http";`); import http from "http";`);

View file

@ -2,13 +2,14 @@ import { readFileSync, writeFileSync } from "fs";
import { minify } from "terser"; import { minify } from "terser";
const DISABLE = false; const DISABLE = false;
writeFileSync("./build/.MANGLED", `${DISABLE}`);
if (DISABLE) { if (DISABLE) {
writeFileSync("./build/Binato.min.js", readFileSync("./build/combined.js")); writeFileSync("./build/Binato.js", readFileSync("./build/index.js"));
console.warn("[WARNING] mangle.ts is disabled!"); console.warn("[WARNING] mangle.ts is disabled!");
} else { } else {
(async () => { (async () => {
const mangled = await minify(readFileSync("./build/combined.js").toString(), { const mangled = await minify(readFileSync("./build/index.js").toString(), {
mangle: true, mangle: true,
toplevel: true, toplevel: true,
}); });