progress
This commit is contained in:
parent
1a871e4c35
commit
734cebb19e
48 changed files with 2881 additions and 3706 deletions
|
@ -12,12 +12,10 @@ import { ChatHistory } from "./server/ChatHistory";
|
|||
import { Config } from "./server/interfaces/Config";
|
||||
import compression from "compression";
|
||||
import express from "express";
|
||||
import { HandleRequest, GetSharedContent } from "./server/BanchoServer";
|
||||
import { SharedContent } from "./server/interfaces/SharedContent";
|
||||
import { HandleRequest } from "./server/BanchoServer";
|
||||
import { Shared } from "./server/objects/Shared";
|
||||
import { Registry, collectDefaultMetrics } from "prom-client";
|
||||
const config:Config = JSON.parse(readFileSync(__dirname + "/config.json").toString()) as Config;
|
||||
// Pull out shared data from BanchoServer
|
||||
const sharedContent:SharedContent = GetSharedContent();
|
||||
|
||||
const binatoApp:express.Application = express();
|
||||
|
||||
|
@ -55,6 +53,7 @@ binatoApp.use((req, res) => {
|
|||
if (req.url == "/" || req.url == "/index.html" || req.url == "/index") {
|
||||
res.send(INDEX_PAGE);
|
||||
} else if (req.url == "/chat") {
|
||||
// I don't think this works??
|
||||
res.send(ChatHistory.GenerateForWeb());
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import chalk from "chalk";
|
||||
import * as dyetty from "dyetty";
|
||||
|
||||
enum LogType {
|
||||
INFO,
|
||||
|
@ -7,14 +7,14 @@ enum LogType {
|
|||
};
|
||||
|
||||
const LogTags = {
|
||||
INFO: chalk.bgGreen(chalk.black(" INFO ")),
|
||||
BANCHO: chalk.bgMagenta(chalk.black(" BANCHO ")),
|
||||
WEBREQ: chalk.bgGreen(chalk.black(" WEBREQ ")),
|
||||
CHAT: chalk.bgCyan(chalk.black(" CHAT ")),
|
||||
WARN: chalk.bgYellow(chalk.black(" WARN ")),
|
||||
ERROR: chalk.bgRed(" ERRR "),
|
||||
REDIS: chalk.bgRed(chalk.white(" bREDIS ")),
|
||||
STREAM: chalk.bgBlue(chalk.black(" STREAM "))
|
||||
INFO: dyetty.bgGreen(dyetty.black(" INFO ")),
|
||||
BANCHO: dyetty.bgMagenta(dyetty.black(" BANCHO ")),
|
||||
WEBREQ: dyetty.bgGreen(dyetty.black(" WEBREQ ")),
|
||||
CHAT: dyetty.bgCyan(dyetty.black(" CHAT ")),
|
||||
WARN: dyetty.bgYellow(dyetty.black(" WARN ")),
|
||||
ERROR: dyetty.bgRed(" ERRR "),
|
||||
REDIS: dyetty.bgRed(dyetty.white(" bREDIS ")),
|
||||
STREAM: dyetty.bgBlue(dyetty.black(" STREAM "))
|
||||
} as const;
|
||||
|
||||
function correctValue(i:number) : string {
|
||||
|
@ -24,7 +24,7 @@ function correctValue(i:number) : string {
|
|||
|
||||
function getTime() : string {
|
||||
const time = new Date();
|
||||
return chalk.green(`[${correctValue(time.getHours())}:${correctValue(time.getMinutes())}:${correctValue(time.getSeconds())}]`);
|
||||
return dyetty.green(`[${correctValue(time.getHours())}:${correctValue(time.getMinutes())}:${correctValue(time.getSeconds())}]`);
|
||||
}
|
||||
|
||||
function log(tag:string, log:string, logType:LogType = LogType.INFO) : void {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export abstract class Constants {
|
||||
public static readonly DEBUG = false;
|
||||
public static readonly PROTOCOL_VERSION = 19;
|
||||
}
|
18
osuTyping.ts
Normal file
18
osuTyping.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { OsuPacketWriter } from "./server/interfaces/OsuPacketWriter";
|
||||
|
||||
// Dummy file
|
||||
const nodeOsu = require("osu-packet");
|
||||
|
||||
export abstract class osu {
|
||||
static Bancho = {
|
||||
Writer: function() : OsuPacketWriter {
|
||||
return new nodeOsu.Bancho.Writer();
|
||||
}
|
||||
};
|
||||
|
||||
static Client = {
|
||||
Reader: function(data:any) : any {
|
||||
return new nodeOsu.Client.Reader(data);
|
||||
}
|
||||
};
|
||||
}
|
4531
package-lock.json
generated
4531
package-lock.json
generated
File diff suppressed because it is too large
Load diff
35
package.json
35
package.json
|
@ -4,9 +4,13 @@
|
|||
"description": "",
|
||||
"main": "Binato.ts",
|
||||
"scripts": {
|
||||
"dev:updateCheck": "check-outdated",
|
||||
"dev:run": "nodemon --watch './**/*.ts' Binato.ts",
|
||||
"pack": "webpack",
|
||||
"build": "tsc --build",
|
||||
"build": "npm-run-all build:*",
|
||||
"build:smash": "ts-node ./tooling/fileSmasher.ts",
|
||||
"build:build": "tsc --build",
|
||||
"build:mangle": "ts-node ./tooling/mangle.ts",
|
||||
"build:cleanup": "ts-node ./tooling/cleanup.ts",
|
||||
"_clean": "tsc --build --clean"
|
||||
},
|
||||
"keywords": [],
|
||||
|
@ -14,26 +18,25 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aes256": "^1.1.0",
|
||||
"chalk": "^4.1.0",
|
||||
"compression": "^1.7.4",
|
||||
"dyetty": "^1.0.1",
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"mysql2": "^3.6.0",
|
||||
"node-fetch": "^2.6.13",
|
||||
"osu-packet": "^4.1.2",
|
||||
"prom-client": "^14.1.0",
|
||||
"redis": "^4.5.0"
|
||||
"prom-client": "^14.2.0",
|
||||
"redis": "^4.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-loader": "^9.4.1",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/node": "^20.5.1",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"check-outdated": "^2.12.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ts-loader": "^9.4.4",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +1,22 @@
|
|||
import { Config } from "./interfaces/Config";
|
||||
import { ConsoleHelper } from "../ConsoleHelper";
|
||||
import { Channel } from "./objects/Channel";
|
||||
import { ChatManager } from "./ChatManager";
|
||||
import { Database } from "./objects/Database";
|
||||
import { DataStreamArray } from "./objects/DataStreamArray";
|
||||
import { LatLng } from "./objects/LatLng";
|
||||
import { LoginProcess } from "./LoginProcess";
|
||||
import { Packets } from "./enums/Packets";
|
||||
import { replaceAll } from "./Util";
|
||||
import { readFileSync } from "fs";
|
||||
import { RedisClientType, createClient } from "redis";
|
||||
import { Request, Response } from "express";
|
||||
import { SpectatorManager } from "./SpectatorManager";
|
||||
import { UserArray } from "./objects/UserArray";
|
||||
import { User } from "./objects/User";
|
||||
import { MultiplayerManager } from "./MultiplayerManager";
|
||||
import { SharedContent } from "./interfaces/SharedContent";
|
||||
const config:Config = JSON.parse(readFileSync("./config.json").toString()) as Config;
|
||||
// TODO: Port osu-packet to TypeScript
|
||||
const osu = require("osu-packet");
|
||||
import { PrivateMessage } from "./packets/PrivateMessage";
|
||||
import { MessageData } from "./interfaces/MessageData";
|
||||
import { Shared } from "./objects/Shared";
|
||||
|
||||
const sharedContent:any = {};
|
||||
// NOTE: This function should only be used externaly in Binato.ts and in this file.
|
||||
export function GetSharedContent() : SharedContent {
|
||||
return sharedContent;
|
||||
}
|
||||
const shared:Shared = new Shared();
|
||||
shared.database.query("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'");
|
||||
|
||||
const DB:Database = sharedContent.database = new Database(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name, async () => {
|
||||
// Close any unclosed db matches on startup
|
||||
DB.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
|
||||
DB.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
|
||||
});
|
||||
|
||||
// User session storage
|
||||
const users:UserArray = sharedContent.users = new UserArray();
|
||||
|
||||
// Add the bot user
|
||||
const botUser:User = users.add("bot", new User(3, "SillyBot", "bot", GetSharedContent()));
|
||||
// Set the bot's position on the map
|
||||
botUser.location = new LatLng(50, -32);
|
||||
|
||||
// DataStream storage
|
||||
const streams:DataStreamArray = sharedContent.streams = new DataStreamArray();
|
||||
|
||||
// ChatManager
|
||||
const chatManager:ChatManager = sharedContent.chatManager = new ChatManager(GetSharedContent());
|
||||
chatManager.AddChatChannel("osu", "The main channel", true);
|
||||
chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
|
||||
chatManager.AddChatChannel("english", "Talk in exclusively English");
|
||||
chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese");
|
||||
|
||||
const multiplayerManager:MultiplayerManager = sharedContent.mutiplayerManager = new MultiplayerManager(GetSharedContent());
|
||||
|
||||
const spectatorManager:SpectatorManager = new SpectatorManager(GetSharedContent());
|
||||
// Server Setup
|
||||
const spectatorManager:SpectatorManager = new SpectatorManager(shared);
|
||||
|
||||
let redisClient:RedisClientType;
|
||||
|
||||
|
@ -65,10 +29,10 @@ async function subscribeToChannel(channelName:string, callback:(message:string)
|
|||
ConsoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
||||
}
|
||||
|
||||
if (config.redis.enabled) {
|
||||
if (shared.config.redis.enabled) {
|
||||
(async () => {
|
||||
redisClient = createClient({
|
||||
url: `redis://${replaceAll(config.redis.password, " ", "") == "" ? "" : `${config.redis.password}@`}${config.redis.address}:${config.redis.port}/${config.redis.database}`
|
||||
url: `redis://${shared.config.redis.password.replaceAll(" ", "") == "" ? "" : `${shared.config.redis.password}@`}${shared.config.redis.address}:${shared.config.redis.port}/${shared.config.redis.database}`
|
||||
});
|
||||
|
||||
redisClient.on('error', e => ConsoleHelper.printRedis(e));
|
||||
|
@ -79,14 +43,12 @@ if (config.redis.enabled) {
|
|||
|
||||
// Score submit update channel
|
||||
subscribeToChannel("binato:update_user_stats", (message) => {
|
||||
if (typeof(message) === "string") {
|
||||
const user = users.getById(parseInt(message));
|
||||
if (user != null) {
|
||||
// Update user info
|
||||
user.updateUserInfo(true);
|
||||
const user = shared.users.getById(parseInt(message));
|
||||
if (user != null) {
|
||||
// Update user info
|
||||
user.updateUserInfo(true);
|
||||
|
||||
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
||||
}
|
||||
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@ -98,10 +60,18 @@ import { Logout } from "./packets/Logout";
|
|||
import { UserPresence } from "./packets/UserPresence";
|
||||
import { UserStatsRequest } from "./packets/UserStatsRequest";
|
||||
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
||||
import { TourneyMatchSpecialInfo } from "./packets/TourneyMatchSpecialInfo";
|
||||
import { osu } from "../osuTyping";
|
||||
import { TourneyMatchJoinChannel } from "./packets/TourneyJoinMatchChannel";
|
||||
import { TourneyMatchLeaveChannel } from "./packets/TourneyMatchLeaveChannel";
|
||||
import { AddFriend } from "./packets/AddFriend";
|
||||
import { RemoveFriend } from "./packets/RemoveFriend";
|
||||
import { PrivateChannel } from "./objects/PrivateChannel";
|
||||
import { Constants } from "../Constants";
|
||||
|
||||
// User timeout interval
|
||||
setInterval(() => {
|
||||
for (let User of users.getIterableItems()) {
|
||||
for (let User of shared.users.getIterableItems()) {
|
||||
if (User.uuid == "bot") continue; // Ignore the bot
|
||||
|
||||
// Logout this user, they're clearly gone.
|
||||
|
@ -114,29 +84,29 @@ setInterval(() => {
|
|||
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||
|
||||
export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||
// Remove headers we don't need for Bancho
|
||||
res.removeHeader('X-Powered-By');
|
||||
res.removeHeader('Date');
|
||||
|
||||
// Get the client's token string and request data
|
||||
const requestTokenString:string | undefined = req.header("osu-token");
|
||||
|
||||
// Check if the user is logged in
|
||||
if (requestTokenString == null) {
|
||||
// Only do this if we're absolutely sure that we're connected to the DB
|
||||
if (DB.connected) {
|
||||
if (shared.database.connected) {
|
||||
// Client doesn't have a token yet, let's auth them!
|
||||
|
||||
await LoginProcess(req, res, packet, GetSharedContent());
|
||||
DB.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [users.getLength() - 1]);
|
||||
await LoginProcess(req, res, packet, shared);
|
||||
shared.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [shared.users.getLength() - 1]);
|
||||
}
|
||||
} else {
|
||||
let responseData:Buffer | string = EMPTY_BUFFER;
|
||||
|
||||
// Remove headers we don't need for Bancho
|
||||
res.removeHeader('X-Powered-By');
|
||||
res.removeHeader('Date'); // This is not spec compilant
|
||||
|
||||
// Client has a token, let's see what they want.
|
||||
try {
|
||||
// Get the current user
|
||||
const PacketUser:User | undefined = users.getByToken(requestTokenString);
|
||||
const PacketUser:User | undefined = shared.users.getByToken(requestTokenString);
|
||||
|
||||
// Make sure the client's token isn't invalid
|
||||
if (PacketUser != null) {
|
||||
|
@ -144,7 +114,7 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
|||
PacketUser.timeoutTime = Date.now() + 60000;
|
||||
|
||||
// Create a new osu! packet reader
|
||||
const osuPacketReader = new osu.Client.Reader(packet);
|
||||
const osuPacketReader = osu.Client.Reader(packet);
|
||||
// Parse current bancho packet
|
||||
const PacketData = osuPacketReader.Parse();
|
||||
|
||||
|
@ -156,7 +126,8 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
|||
break;
|
||||
|
||||
case Packets.Client_SendPublicMessage:
|
||||
let channel = chatManager.GetChannelByName(CurrentPacket.data.target);
|
||||
const message:MessageData = CurrentPacket.data;
|
||||
let channel = shared.chatManager.GetChannelByName(message.target);
|
||||
if (channel instanceof Channel) {
|
||||
channel.SendMessage(PacketUser, CurrentPacket.data.message);
|
||||
}
|
||||
|
@ -183,23 +154,23 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
|||
break;
|
||||
|
||||
case Packets.Client_SendPrivateMessage:
|
||||
//SendPrivateMessage(PacketUser, CurrentPacket.data);
|
||||
PrivateMessage(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_JoinLobby:
|
||||
multiplayerManager.JoinLobby(PacketUser);
|
||||
shared.multiplayerManager.JoinLobby(PacketUser);
|
||||
break;
|
||||
|
||||
case Packets.Client_PartLobby:
|
||||
multiplayerManager.LeaveLobby(PacketUser);
|
||||
shared.multiplayerManager.LeaveLobby(PacketUser);
|
||||
break;
|
||||
|
||||
case Packets.Client_CreateMatch:
|
||||
await multiplayerManager.CreateMatch(PacketUser, CurrentPacket.data);
|
||||
await shared.multiplayerManager.CreateMatch(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_JoinMatch:
|
||||
multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data);
|
||||
shared.multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_MatchChangeSlot:
|
||||
|
@ -284,11 +255,11 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
|||
break;
|
||||
|
||||
case Packets.Client_FriendAdd:
|
||||
//AddFriend(PacketUser, CurrentPacket.data);
|
||||
AddFriend(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_FriendRemove:
|
||||
//RemoveFriend(PacketUser, CurrentPacket.data);
|
||||
RemoveFriend(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_UserStatsRequest:
|
||||
|
@ -296,15 +267,15 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
|||
break;
|
||||
|
||||
case Packets.Client_SpecialMatchInfoRequest:
|
||||
//TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
||||
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_SpecialJoinMatchChannel:
|
||||
//TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
||||
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_SpecialLeaveMatchChannel:
|
||||
//TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
||||
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case Packets.Client_Invite:
|
||||
|
@ -328,14 +299,18 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
|||
PacketUser.clearQueue();
|
||||
} else {
|
||||
// Only do this if we're absolutely sure that we're connected to the DB
|
||||
if (DB.connected) {
|
||||
if (shared.database.connected) {
|
||||
// User's token is invlid, force a reconnect
|
||||
ConsoleHelper.printBancho(`Forced client re-login (Token is invalid)`);
|
||||
responseData = "\u0005\u0000\u0000\u0004\u0000\u0000\u0000<30><30><EFBFBD><EFBFBD>\u0018\u0000\u0000\u0011\u0000\u0000\u0000\u000b\u000fReconnecting...";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (Constants.DEBUG) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
ConsoleHelper.printError(`${e}`);
|
||||
} finally {
|
||||
res.writeHead(200, {
|
||||
"Connection": "keep-alive",
|
||||
|
|
34
server/Bot.ts
Normal file
34
server/Bot.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { ICommand } from "./interfaces/ICommand";
|
||||
import { Channel } from "./objects/Channel";
|
||||
import { Shared } from "./objects/Shared";
|
||||
import { User } from "./objects/User";
|
||||
|
||||
// Commands
|
||||
import { RankingCommand } from "./commands/Ranking";
|
||||
import { LockCommand } from "./commands/Lock";
|
||||
import { MultiplayerCommands } from "./commands/Multiplayer";
|
||||
import { HelpCommand } from "./commands/Help";
|
||||
import { RollCommand } from "./commands/RollCommand";
|
||||
|
||||
export class Bot {
|
||||
public user:User;
|
||||
private commands:{ [id: string]: ICommand } = {};
|
||||
|
||||
public constructor(shared:Shared, botUser:User) {
|
||||
this.user = botUser;
|
||||
|
||||
this.commands["help"] = new HelpCommand(shared, this.commands);
|
||||
this.commands["ranking"] = new RankingCommand(shared);
|
||||
this.commands["lock"] = new LockCommand(shared);
|
||||
this.commands["mp"] = new MultiplayerCommands(shared);
|
||||
this.commands["roll"] = new RollCommand(shared);
|
||||
}
|
||||
|
||||
public OnMessage(channel:Channel, sender:User, text:string) {
|
||||
const args = text.split(" ");
|
||||
const command = this.commands[`${args.shift()?.replace("!", "").toLowerCase()}`];
|
||||
if (command) {
|
||||
command.exec(channel, sender, args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,21 +2,22 @@ import { Channel } from "./objects/Channel";
|
|||
import { ConsoleHelper } from "../ConsoleHelper";
|
||||
import { FunkyArray } from "./objects/FunkyArray";
|
||||
import { User } from "./objects/User";
|
||||
import { SharedContent } from "./interfaces/SharedContent";
|
||||
const osu = require("osu-packet");
|
||||
import { Shared } from "./objects/Shared";
|
||||
import { osu } from "../osuTyping";
|
||||
import { PrivateChannel } from "./objects/PrivateChannel";
|
||||
|
||||
export class ChatManager {
|
||||
public chatChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
||||
public forceJoinChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
||||
private readonly sharedContent:SharedContent;
|
||||
private readonly shared:Shared;
|
||||
|
||||
public constructor(sharedContent:SharedContent) {
|
||||
this.sharedContent = sharedContent;
|
||||
public constructor(shared:Shared) {
|
||||
this.shared = shared;
|
||||
}
|
||||
|
||||
public AddChatChannel(name:string, description:string, forceJoin:boolean = false) : Channel {
|
||||
const stream = this.sharedContent.streams.CreateStream(`chat_channel:${name}`, false);
|
||||
const channel = new Channel(this.sharedContent, `#${name}`, description, stream);
|
||||
const stream = this.shared.streams.CreateStream(`chat_channel:${name}`, false);
|
||||
const channel = new Channel(this.shared, `#${name}`, description, stream);
|
||||
this.chatChannels.add(channel.name, channel);
|
||||
if (forceJoin) {
|
||||
this.forceJoinChannels.add(name, channel);
|
||||
|
@ -26,8 +27,8 @@ export class ChatManager {
|
|||
}
|
||||
|
||||
public AddSpecialChatChannel(name:string, streamName:string, forceJoin:boolean = false) : Channel {
|
||||
const stream = this.sharedContent.streams.CreateStream(`chat_channel:${streamName}`, false);
|
||||
const channel = new Channel(this.sharedContent, `#${name}`, "", stream);
|
||||
const stream = this.shared.streams.CreateStream(`chat_channel:${streamName}`, false);
|
||||
const channel = new Channel(this.shared, `#${name}`, "", stream);
|
||||
this.chatChannels.add(channel.name, channel);
|
||||
if (forceJoin) {
|
||||
this.forceJoinChannels.add(name, channel);
|
||||
|
@ -51,18 +52,31 @@ export class ChatManager {
|
|||
}
|
||||
}
|
||||
|
||||
public AddPrivateChatChannel(user0:User, user1:User) {
|
||||
const stream = this.shared.streams.CreateStream(`private_channel:${user0.username},${user1.username}`, true);
|
||||
const channel = new PrivateChannel(user0, user1, stream);
|
||||
this.chatChannels.add(channel.name, channel);
|
||||
ConsoleHelper.printChat(`Created private chat channel [${channel.name}]`);
|
||||
return channel;
|
||||
}
|
||||
|
||||
public GetChannelByName(channelName:string) : Channel | undefined {
|
||||
return this.chatChannels.getByKey(channelName);
|
||||
}
|
||||
|
||||
public GetPrivateChannelByName(channelName:string) : Channel | undefined {
|
||||
return this.chatChannels.getByKey(channelName);
|
||||
}
|
||||
|
||||
public ForceJoinChannels(user:User) {
|
||||
for (let channel of this.forceJoinChannels.getIterableItems()) {
|
||||
channel.Join(user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SendChannelListing(user:User) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
for (let channel of this.chatChannels.getIterableItems()) {
|
||||
if (channel.isSpecial) {
|
||||
continue;
|
||||
|
|
|
@ -1,264 +1,270 @@
|
|||
const countryCodes = {
|
||||
LV: 132,
|
||||
AD: 3,
|
||||
LT: 130,
|
||||
KM: 116,
|
||||
QA: 182,
|
||||
VA: 0,
|
||||
PK: 173,
|
||||
KI: 115,
|
||||
SS: 0,
|
||||
KH: 114,
|
||||
NZ: 166,
|
||||
TO: 215,
|
||||
KZ: 122,
|
||||
GA: 76,
|
||||
BW: 35,
|
||||
AX: 247,
|
||||
GE: 79,
|
||||
UA: 222,
|
||||
CR: 50,
|
||||
AE: 0,
|
||||
NE: 157,
|
||||
ZA: 240,
|
||||
SK: 196,
|
||||
BV: 34,
|
||||
SH: 0,
|
||||
PT: 179,
|
||||
SC: 189,
|
||||
CO: 49,
|
||||
GP: 86,
|
||||
GY: 93,
|
||||
CM: 47,
|
||||
TJ: 211,
|
||||
AF: 5,
|
||||
IE: 101,
|
||||
AL: 8,
|
||||
BG: 24,
|
||||
JO: 110,
|
||||
MU: 149,
|
||||
PM: 0,
|
||||
LA: 0,
|
||||
IO: 104,
|
||||
KY: 121,
|
||||
SA: 187,
|
||||
KN: 0,
|
||||
OM: 167,
|
||||
CY: 54,
|
||||
BQ: 0,
|
||||
BT: 33,
|
||||
WS: 236,
|
||||
ES: 67,
|
||||
LR: 128,
|
||||
RW: 186,
|
||||
AQ: 12,
|
||||
PW: 180,
|
||||
JE: 250,
|
||||
TN: 214,
|
||||
ZW: 243,
|
||||
JP: 111,
|
||||
BB: 20,
|
||||
VN: 233,
|
||||
HN: 96,
|
||||
KP: 0,
|
||||
WF: 235,
|
||||
EC: 62,
|
||||
HU: 99,
|
||||
GF: 80,
|
||||
GQ: 87,
|
||||
TW: 220,
|
||||
MC: 135,
|
||||
BE: 22,
|
||||
PN: 176,
|
||||
SZ: 205,
|
||||
CZ: 55,
|
||||
LY: 0,
|
||||
IN: 103,
|
||||
FM: 0,
|
||||
PY: 181,
|
||||
PH: 172,
|
||||
MN: 142,
|
||||
GG: 248,
|
||||
CC: 39,
|
||||
ME: 242,
|
||||
DO: 60,
|
||||
KR: 0,
|
||||
PL: 174,
|
||||
MT: 148,
|
||||
MM: 141,
|
||||
AW: 17,
|
||||
MV: 150,
|
||||
BD: 21,
|
||||
NR: 164,
|
||||
AT: 15,
|
||||
GW: 92,
|
||||
FR: 74,
|
||||
LI: 126,
|
||||
CF: 41,
|
||||
DZ: 61,
|
||||
MA: 134,
|
||||
VG: 0,
|
||||
NC: 156,
|
||||
IQ: 105,
|
||||
BN: 0,
|
||||
BF: 23,
|
||||
BO: 30,
|
||||
GB: 77,
|
||||
CU: 51,
|
||||
LU: 131,
|
||||
YT: 238,
|
||||
NO: 162,
|
||||
SM: 198,
|
||||
GL: 83,
|
||||
IS: 107,
|
||||
AO: 11,
|
||||
MH: 138,
|
||||
SE: 191,
|
||||
ZM: 241,
|
||||
FJ: 70,
|
||||
SL: 197,
|
||||
CH: 43,
|
||||
RU: 0,
|
||||
CW: 0,
|
||||
CX: 53,
|
||||
TF: 208,
|
||||
NL: 161,
|
||||
AU: 16,
|
||||
FI: 69,
|
||||
MS: 147,
|
||||
GH: 81,
|
||||
BY: 36,
|
||||
IL: 102,
|
||||
VC: 0,
|
||||
NG: 159,
|
||||
HT: 98,
|
||||
LS: 129,
|
||||
MR: 146,
|
||||
YE: 237,
|
||||
MP: 144,
|
||||
SX: 0,
|
||||
RE: 183,
|
||||
RO: 184,
|
||||
NP: 163,
|
||||
CG: 0,
|
||||
FO: 73,
|
||||
CI: 0,
|
||||
TH: 210,
|
||||
HK: 94,
|
||||
TK: 212,
|
||||
XK: 0,
|
||||
DM: 59,
|
||||
LC: 0,
|
||||
ID: 100,
|
||||
MG: 137,
|
||||
JM: 109,
|
||||
IT: 108,
|
||||
CA: 38,
|
||||
TZ: 221,
|
||||
GI: 82,
|
||||
KG: 113,
|
||||
NU: 165,
|
||||
TV: 219,
|
||||
LB: 124,
|
||||
SY: 0,
|
||||
PR: 177,
|
||||
NI: 160,
|
||||
KE: 112,
|
||||
MO: 0,
|
||||
SR: 201,
|
||||
VI: 0,
|
||||
SV: 203,
|
||||
HM: 0,
|
||||
CD: 0,
|
||||
BI: 26,
|
||||
BM: 28,
|
||||
MW: 151,
|
||||
TM: 213,
|
||||
GT: 90,
|
||||
AG: 0,
|
||||
UM: 0,
|
||||
US: 225,
|
||||
AR: 13,
|
||||
DJ: 57,
|
||||
KW: 120,
|
||||
MY: 153,
|
||||
FK: 71,
|
||||
EG: 64,
|
||||
BA: 0,
|
||||
CN: 48,
|
||||
GN: 85,
|
||||
PS: 178,
|
||||
SO: 200,
|
||||
IM: 249,
|
||||
GS: 0,
|
||||
BR: 31,
|
||||
GM: 84,
|
||||
PF: 170,
|
||||
PA: 168,
|
||||
PG: 171,
|
||||
BH: 25,
|
||||
TG: 209,
|
||||
GU: 91,
|
||||
CK: 45,
|
||||
MF: 252,
|
||||
VE: 230,
|
||||
CL: 46,
|
||||
TR: 217,
|
||||
UG: 223,
|
||||
GD: 78,
|
||||
TT: 218,
|
||||
TL: 0,
|
||||
MD: 0,
|
||||
MK: 0,
|
||||
ST: 202,
|
||||
CV: 52,
|
||||
MQ: 145,
|
||||
GR: 88,
|
||||
HR: 97,
|
||||
BZ: 37,
|
||||
UZ: 227,
|
||||
DK: 58,
|
||||
SN: 199,
|
||||
ET: 68,
|
||||
VU: 234,
|
||||
ER: 66,
|
||||
BJ: 27,
|
||||
LK: 127,
|
||||
NA: 155,
|
||||
AS: 14,
|
||||
SG: 192,
|
||||
PE: 169,
|
||||
IR: 0,
|
||||
MX: 152,
|
||||
TD: 207,
|
||||
AZ: 18,
|
||||
AM: 9,
|
||||
BL: 0,
|
||||
SJ: 195,
|
||||
SB: 188,
|
||||
NF: 158,
|
||||
RS: 239,
|
||||
DE: 56,
|
||||
EH: 65,
|
||||
EE: 63,
|
||||
SD: 190,
|
||||
ML: 140,
|
||||
TC: 206,
|
||||
MZ: 154,
|
||||
BS: 32,
|
||||
UY: 226,
|
||||
SI: 194,
|
||||
AI: 7
|
||||
enum CountryCodes {
|
||||
LV = 132,
|
||||
AD = 3,
|
||||
LT = 130,
|
||||
KM = 116,
|
||||
QA = 182,
|
||||
VA = 0,
|
||||
PK = 173,
|
||||
KI = 115,
|
||||
SS = 0,
|
||||
KH = 114,
|
||||
NZ = 166,
|
||||
TO = 215,
|
||||
KZ = 122,
|
||||
BW = 35,
|
||||
GA = 76,
|
||||
AX = 247,
|
||||
GE = 79,
|
||||
UA = 222,
|
||||
CR = 50,
|
||||
AE = 0,
|
||||
NE = 157,
|
||||
ZA = 240,
|
||||
SK = 196,
|
||||
BV = 34,
|
||||
SH = 0,
|
||||
PT = 179,
|
||||
SC = 189,
|
||||
CO = 49,
|
||||
GP = 86,
|
||||
GY = 93,
|
||||
CM = 47,
|
||||
TJ = 211,
|
||||
AF = 5,
|
||||
IE = 101,
|
||||
AL = 8,
|
||||
BG = 24,
|
||||
JO = 110,
|
||||
MU = 149,
|
||||
PM = 0,
|
||||
LA = 0,
|
||||
IO = 104,
|
||||
KY = 121,
|
||||
SA = 187,
|
||||
KN = 0,
|
||||
OM = 167,
|
||||
CY = 54,
|
||||
BQ = 0,
|
||||
BT = 33,
|
||||
WS = 236,
|
||||
ES = 67,
|
||||
LR = 128,
|
||||
RW = 186,
|
||||
AQ = 12,
|
||||
PW = 180,
|
||||
JE = 250,
|
||||
TN = 214,
|
||||
ZW = 243,
|
||||
JP = 111,
|
||||
BB = 20,
|
||||
VN = 233,
|
||||
HN = 96,
|
||||
KP = 0,
|
||||
WF = 235,
|
||||
EC = 62,
|
||||
HU = 99,
|
||||
GF = 80,
|
||||
GQ = 87,
|
||||
TW = 220,
|
||||
MC = 135,
|
||||
BE = 22,
|
||||
PN = 176,
|
||||
SZ = 205,
|
||||
CZ = 55,
|
||||
LY = 0,
|
||||
IN = 103,
|
||||
FM = 0,
|
||||
PY = 181,
|
||||
PH = 172,
|
||||
MN = 142,
|
||||
GG = 248,
|
||||
CC = 39,
|
||||
ME = 242,
|
||||
DO = 60,
|
||||
KR = 0,
|
||||
PL = 174,
|
||||
MT = 148,
|
||||
MM = 141,
|
||||
AW = 17,
|
||||
MV = 150,
|
||||
BD = 21,
|
||||
NR = 164,
|
||||
AT = 15,
|
||||
GW = 92,
|
||||
FR = 74,
|
||||
LI = 126,
|
||||
CF = 41,
|
||||
DZ = 61,
|
||||
MA = 134,
|
||||
VG = 0,
|
||||
NC = 156,
|
||||
IQ = 105,
|
||||
BN = 0,
|
||||
BF = 23,
|
||||
BO = 30,
|
||||
GB = 77,
|
||||
CU = 51,
|
||||
LU = 131,
|
||||
YT = 238,
|
||||
NO = 162,
|
||||
SM = 198,
|
||||
GL = 83,
|
||||
IS = 107,
|
||||
AO = 11,
|
||||
MH = 138,
|
||||
SE = 191,
|
||||
ZM = 241,
|
||||
FJ = 70,
|
||||
SL = 197,
|
||||
CH = 43,
|
||||
RU = 0,
|
||||
CW = 0,
|
||||
CX = 53,
|
||||
TF = 208,
|
||||
NL = 161,
|
||||
AU = 16,
|
||||
FI = 69,
|
||||
MS = 147,
|
||||
GH = 81,
|
||||
BY = 36,
|
||||
IL = 102,
|
||||
VC = 0,
|
||||
NG = 159,
|
||||
HT = 98,
|
||||
LS = 129,
|
||||
MR = 146,
|
||||
YE = 237,
|
||||
MP = 144,
|
||||
SX = 0,
|
||||
RE = 183,
|
||||
RO = 184,
|
||||
NP = 163,
|
||||
CG = 0,
|
||||
FO = 73,
|
||||
CI = 0,
|
||||
TH = 210,
|
||||
HK = 94,
|
||||
TK = 212,
|
||||
XK = 0,
|
||||
DM = 59,
|
||||
LC = 0,
|
||||
ID = 100,
|
||||
MG = 137,
|
||||
JM = 109,
|
||||
IT = 108,
|
||||
CA = 38,
|
||||
TZ = 221,
|
||||
GI = 82,
|
||||
KG = 113,
|
||||
NU = 165,
|
||||
TV = 219,
|
||||
LB = 124,
|
||||
SY = 0,
|
||||
PR = 177,
|
||||
NI = 160,
|
||||
KE = 112,
|
||||
MO = 0,
|
||||
SR = 201,
|
||||
VI = 0,
|
||||
SV = 203,
|
||||
HM = 0,
|
||||
CD = 0,
|
||||
BI = 26,
|
||||
BM = 28,
|
||||
MW = 151,
|
||||
TM = 213,
|
||||
GT = 90,
|
||||
AG = 0,
|
||||
UM = 0,
|
||||
US = 225,
|
||||
AR = 13,
|
||||
DJ = 57,
|
||||
KW = 120,
|
||||
MY = 153,
|
||||
FK = 71,
|
||||
EG = 64,
|
||||
BA = 0,
|
||||
CN = 48,
|
||||
GN = 85,
|
||||
PS = 178,
|
||||
SO = 200,
|
||||
IM = 249,
|
||||
GS = 0,
|
||||
BR = 31,
|
||||
GM = 84,
|
||||
PF = 170,
|
||||
PA = 168,
|
||||
PG = 171,
|
||||
BH = 25,
|
||||
TG = 209,
|
||||
GU = 91,
|
||||
CK = 45,
|
||||
MF = 252,
|
||||
VE = 230,
|
||||
CL = 46,
|
||||
TR = 217,
|
||||
UG = 223,
|
||||
GD = 78,
|
||||
TT = 218,
|
||||
TL = 0,
|
||||
MD = 0,
|
||||
MK = 0,
|
||||
ST = 202,
|
||||
CV = 52,
|
||||
MQ = 145,
|
||||
GR = 88,
|
||||
HR = 97,
|
||||
BZ = 37,
|
||||
UZ = 227,
|
||||
DK = 58,
|
||||
SN = 199,
|
||||
ET = 68,
|
||||
VU = 234,
|
||||
ER = 66,
|
||||
BJ = 27,
|
||||
LK = 127,
|
||||
NA = 155,
|
||||
AS = 14,
|
||||
SG = 192,
|
||||
PE = 169,
|
||||
IR = 0,
|
||||
MX = 152,
|
||||
TD = 207,
|
||||
AZ = 18,
|
||||
AM = 9,
|
||||
BL = 0,
|
||||
SJ = 195,
|
||||
SB = 188,
|
||||
NF = 158,
|
||||
RS = 239,
|
||||
DE = 56,
|
||||
EH = 65,
|
||||
EE = 63,
|
||||
SD = 190,
|
||||
ML = 140,
|
||||
TC = 206,
|
||||
MZ = 154,
|
||||
BS = 32,
|
||||
UY = 226,
|
||||
SI = 194,
|
||||
AI = 7
|
||||
};
|
||||
|
||||
const countryCodeKeys = Object.keys(countryCodes);
|
||||
const keys = Object.keys(CountryCodes);
|
||||
const values = Object.values(CountryCodes);
|
||||
|
||||
export function getCountryID(code:string) : number {
|
||||
// Get id of a country from a 2 char code
|
||||
/*const upperCode:string = code.toUpperCase();
|
||||
if (code in countryCodes) {
|
||||
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;
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { ConsoleHelper } from "../ConsoleHelper";
|
||||
import { Database } from "./objects/Database";
|
||||
import fetch from "node-fetch";
|
||||
import { getCountryID } from "./Country";
|
||||
import { generateSession } from "./Util";
|
||||
|
@ -7,46 +6,21 @@ import { LatLng } from "./objects/LatLng";
|
|||
import { LoginInfo } from "./objects/LoginInfo";
|
||||
import { Logout } from "./packets/Logout";
|
||||
import { pbkdf2 } from "crypto";
|
||||
import { readFileSync } from "fs";
|
||||
import { Request, Response } from "express";
|
||||
import { UserArray } from "./objects/UserArray";
|
||||
import { User } from "./objects/User";
|
||||
import { DataStreamArray } from "./objects/DataStreamArray";
|
||||
import { ChatManager } from "./ChatManager";
|
||||
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
||||
import { UserPresence } from "./packets/UserPresence";
|
||||
import { StatusUpdate } from "./packets/StatusUpdate";
|
||||
import { SharedContent } from "./interfaces/SharedContent";
|
||||
const config:any = JSON.parse(readFileSync("./config.json").toString());
|
||||
import { Shared } from "./objects/Shared";
|
||||
import { osu } from "../osuTyping";
|
||||
import { IpZxqResponse } from "./interfaces/IpZxqResponse";
|
||||
const { decrypt: aesDecrypt } = require("aes256");
|
||||
const osu = require("osu-packet");
|
||||
|
||||
function incorrectLoginResponse() {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.LoginReply(-1);
|
||||
return [
|
||||
osuPacketWriter.toBuffer,
|
||||
{
|
||||
'cho-protocol': 19,
|
||||
'Connection': 'keep-alive',
|
||||
'Keep-Alive': 'timeout=5, max=100',
|
||||
}
|
||||
];
|
||||
}
|
||||
const incorrectLoginResponse:Buffer = osu.Bancho.Writer().LoginReply(-1).toBuffer;
|
||||
|
||||
function requiredPWChangeResponse() {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.Announce("As part of migration to a new password system you are required to change your password. Please log in on the website and change your password.");
|
||||
osuPacketWriter.LoginReply(-1);
|
||||
return [
|
||||
osuPacketWriter.toBuffer,
|
||||
{
|
||||
'cho-protocol': 19,
|
||||
'Connection': 'keep-alive',
|
||||
'Keep-Alive': 'timeout=5, max=100',
|
||||
}
|
||||
];
|
||||
}
|
||||
const requiredPWChangeResponse:Buffer = osu.Bancho.Writer()
|
||||
.LoginReply(-1)
|
||||
.Announce("As part of migration to a new password system you are required to change your password. Please logon to the website and change your password.").toBuffer;
|
||||
|
||||
enum LoginTypes {
|
||||
CURRENT,
|
||||
|
@ -54,172 +28,210 @@ enum LoginTypes {
|
|||
OLD_AES
|
||||
}
|
||||
|
||||
function TestLogin(loginInfo:LoginInfo | undefined, database:Database) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// Check if there is any login information provided
|
||||
if (loginInfo == null) return resolve(incorrectLoginResponse());
|
||||
enum LoginResult {
|
||||
VALID,
|
||||
MIGRATION,
|
||||
INCORRECT,
|
||||
}
|
||||
|
||||
const userDBData:any = await database.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||
function TestLogin(loginInfo:LoginInfo, shared:Shared) {
|
||||
return new Promise<LoginResult>(async (resolve, reject) => {
|
||||
const userDBData:any = await shared.database.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||
|
||||
// Make sure a user was found in the database
|
||||
if (userDBData == null) return resolve(incorrectLoginResponse());
|
||||
if (userDBData == null) return resolve(LoginResult.INCORRECT);
|
||||
// Make sure the username is the same as the login info
|
||||
if (userDBData.username !== loginInfo.username) return resolve(incorrectLoginResponse());
|
||||
/*
|
||||
1: Old MD5 password
|
||||
2: Old AES password
|
||||
*/
|
||||
if (userDBData.username !== loginInfo.username) return resolve(LoginResult.INCORRECT);
|
||||
|
||||
console.log(userDBData.has_old_password);
|
||||
switch (userDBData.has_old_password) {
|
||||
case LoginTypes.CURRENT:
|
||||
pbkdf2(loginInfo.password, userDBData.password_salt, config.database.pbkdf2.itterations, config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
||||
pbkdf2(loginInfo.password, userDBData.password_salt, shared.config.database.pbkdf2.itterations, shared.config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else {
|
||||
if (derivedKey.toString("hex") !== userDBData.password_hash)
|
||||
return resolve(incorrectLoginResponse());
|
||||
return resolve(LoginResult.INCORRECT);
|
||||
|
||||
return resolve(undefined); // We good
|
||||
return resolve(LoginResult.VALID); // We good
|
||||
}
|
||||
});
|
||||
break;
|
||||
case LoginTypes.OLD_AES:
|
||||
if (aesDecrypt(config.database.key, userDBData.password_hash) !== loginInfo.password) {
|
||||
return resolve(resolve(incorrectLoginResponse()));
|
||||
console.log("OLD AES");
|
||||
if (aesDecrypt(shared.config.database.key, userDBData.password_hash) !== loginInfo.password) {
|
||||
return resolve(LoginResult.INCORRECT);
|
||||
}
|
||||
return resolve(requiredPWChangeResponse());
|
||||
console.log("correct password");
|
||||
return resolve(LoginResult.MIGRATION);
|
||||
case LoginTypes.OLD_MD5:
|
||||
if (userDBData.password_hash !== loginInfo.password) {
|
||||
return resolve(incorrectLoginResponse());
|
||||
return resolve(LoginResult.INCORRECT);
|
||||
}
|
||||
return resolve(requiredPWChangeResponse());
|
||||
return resolve(LoginResult.MIGRATION);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function LoginProcess(req:Request, res:Response, packet:Buffer, sharedContent:SharedContent) {
|
||||
const loginInfo = LoginInfo.From(packet);
|
||||
export async function LoginProcess(req:Request, res:Response, packet:Buffer, shared:Shared) {
|
||||
const loginStartTime = Date.now();
|
||||
const loginInfo = LoginInfo.From(packet);
|
||||
|
||||
const loginCheck:any = await TestLogin(loginInfo, sharedContent.database);
|
||||
if (loginCheck != null) {
|
||||
res.writeHead(200, loginCheck[1]);
|
||||
return res.end(loginCheck[0]);
|
||||
// Send back no data if there's no loginInfo
|
||||
// Somebody is doing something funky
|
||||
if (loginInfo === undefined) {
|
||||
return res.end("");
|
||||
}
|
||||
|
||||
if (loginInfo == null)
|
||||
return;
|
||||
const loginResult:LoginResult = await TestLogin(loginInfo, shared);
|
||||
let osuPacketWriter = osu.Bancho.Writer();
|
||||
let newUser:User | undefined;
|
||||
let friendsPresence:Buffer = Buffer.alloc(0);
|
||||
|
||||
ConsoleHelper.printBancho(`New client connection. [User: ${loginInfo.username}]`);
|
||||
if (loginResult === LoginResult.VALID && loginInfo !== undefined) {
|
||||
ConsoleHelper.printBancho(`New client connection. [User: ${loginInfo.username}]`);
|
||||
|
||||
// Get users IP for getting location
|
||||
// Get cloudflare requestee IP first
|
||||
let requestIP = req.get("cf-connecting-ip");
|
||||
// Get users IP for getting location
|
||||
// Get cloudflare requestee IP first
|
||||
let requestIP = req.get("cf-connecting-ip");
|
||||
|
||||
// Get IP of requestee since we are probably behind a reverse proxy
|
||||
if (requestIP == null)
|
||||
requestIP = req.get("X-Real-IP");
|
||||
|
||||
// Just get the requestee IP (we are not behind a reverse proxy)
|
||||
// if (requestIP == null)
|
||||
// requestIP = req.remote_addr;
|
||||
|
||||
// Make sure requestIP is never null
|
||||
if (requestIP == null)
|
||||
requestIP = "";
|
||||
|
||||
|
||||
let userCountryCode:string, userLocation:LatLng;
|
||||
// Check if it is a local or null IP
|
||||
if (!requestIP.includes("192.168.") && !requestIP.includes("127.0.") && requestIP != "") {
|
||||
// Set location to null island
|
||||
userCountryCode = "XX";
|
||||
userLocation = new LatLng(0, 0);
|
||||
} else {
|
||||
// Get user's location using zxq
|
||||
const userLocationRequest = await fetch(`https://ip.zxq.co/${requestIP}`);
|
||||
const userLocationData:any = await userLocationRequest.json();
|
||||
const userLatLng:Array<string> = userLocationData.loc.split(",");
|
||||
userCountryCode = userLocationData.country;
|
||||
userLocation = new LatLng(parseFloat(userLatLng[0]), parseFloat(userLatLng[1]));
|
||||
}
|
||||
|
||||
// Get information about the user from the database
|
||||
const userDB = await sharedContent.database.query("SELECT id FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||
|
||||
// Create a token for the client
|
||||
const newClientToken:string = await generateSession();
|
||||
const isTourneyClient = loginInfo.version.includes("tourney");
|
||||
|
||||
// Make sure user is not already connected, kick off if so.
|
||||
const connectedUser = sharedContent.users.getByUsername(loginInfo.username);
|
||||
if (connectedUser != null && !isTourneyClient && !connectedUser.isTourneyUser) {
|
||||
Logout(connectedUser);
|
||||
}
|
||||
|
||||
// Retreive the newly created user
|
||||
const newUser:User = sharedContent.users.add(newClientToken, new User(userDB.id, loginInfo.username, newClientToken, sharedContent));
|
||||
// Set tourney client flag
|
||||
newUser.isTourneyUser = isTourneyClient;
|
||||
newUser.location = userLocation;
|
||||
|
||||
// Get user's data from the database
|
||||
newUser.updateUserInfo();
|
||||
|
||||
try {
|
||||
// Save the country id for the same reason as above
|
||||
newUser.countryID = getCountryID(userCountryCode);
|
||||
|
||||
// We're ready to start putting together a login packet
|
||||
// Create an osu! Packet writer
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// The reply id is the user's id in any other case than an error in which case negative numbers are used
|
||||
osuPacketWriter.LoginReply(newUser.id);
|
||||
// Current bancho protocol version. Defined in Binato.js
|
||||
osuPacketWriter.ProtocolNegotiation(19);
|
||||
// Permission level 4 is osu!supporter
|
||||
osuPacketWriter.LoginPermissions(4);
|
||||
|
||||
// After sending the user their friends list send them the online users
|
||||
UserPresenceBundle(newUser);
|
||||
|
||||
// Set title screen image
|
||||
//osuPacketWriter.TitleUpdate("http://puu.sh/jh7t7/20c04029ad.png|https://osu.ppy.sh/news/123912240253");
|
||||
|
||||
// Add user panel data packets
|
||||
UserPresence(newUser, newUser.id);
|
||||
StatusUpdate(newUser, newUser.id);
|
||||
|
||||
// peppy pls, why
|
||||
osuPacketWriter.ChannelListingComplete();
|
||||
|
||||
// Setup chat
|
||||
sharedContent.chatManager.ForceJoinChannels(newUser);
|
||||
sharedContent.chatManager.SendChannelListing(newUser);
|
||||
|
||||
// Construct user's friends list
|
||||
const userFriends = await sharedContent.database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]);
|
||||
const friendsArray:Array<number> = new Array<number>();
|
||||
for (let i = 0; i < userFriends.length; i++) {
|
||||
friendsArray.push(userFriends[i].friendsWith);
|
||||
// Get IP of requestee since we are probably behind a reverse proxy
|
||||
if (requestIP === undefined) {
|
||||
requestIP = req.get("X-Real-IP");
|
||||
}
|
||||
// Send user's friends list
|
||||
osuPacketWriter.FriendsList(friendsArray);
|
||||
|
||||
osuPacketWriter.Announce(`Welcome back ${loginInfo.username}!`);
|
||||
// Just get the requestee IP (we are not behind a reverse proxy)
|
||||
// if (requestIP == null)
|
||||
// requestIP = req.remote_addr;
|
||||
|
||||
res.removeHeader('X-Powered-By');
|
||||
res.removeHeader('Date');
|
||||
// Complete login
|
||||
// Make sure requestIP is never undefined
|
||||
if (requestIP === undefined) {
|
||||
requestIP = "";
|
||||
}
|
||||
|
||||
let userCountryCode:string, userLocation:LatLng;
|
||||
// Check if it is a local or null IP
|
||||
if (requestIP.includes("192.168.") || requestIP.includes("127.0.") || requestIP === "") {
|
||||
// Set location to null island
|
||||
userCountryCode = "XX";
|
||||
userLocation = new LatLng(0, 0);
|
||||
} else {
|
||||
// Get user's location using zxq
|
||||
const userLocationRequest = await fetch(`https://ip.zxq.co/${requestIP}`);
|
||||
const userLocationData:IpZxqResponse = await userLocationRequest.json();
|
||||
const userLatLng = userLocationData.loc.split(",");
|
||||
userCountryCode = userLocationData.country;
|
||||
userLocation = new LatLng(parseFloat(userLatLng[0]), parseFloat(userLatLng[1]));
|
||||
}
|
||||
|
||||
// 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]);
|
||||
|
||||
// Create a token for the client
|
||||
const newClientToken:string = await generateSession();
|
||||
const isTourneyClient = loginInfo.version.includes("tourney");
|
||||
|
||||
// Make sure user is not already connected, kick off if so.
|
||||
const connectedUser = shared.users.getByUsername(loginInfo.username);
|
||||
if (connectedUser != null && !isTourneyClient && !connectedUser.isTourneyUser) {
|
||||
Logout(connectedUser);
|
||||
}
|
||||
|
||||
// Retreive the newly created user
|
||||
newUser = shared.users.add(newClientToken, new User(userDB.id, loginInfo.username, newClientToken, shared));
|
||||
// Set tourney client flag
|
||||
newUser.isTourneyUser = isTourneyClient;
|
||||
newUser.location = userLocation;
|
||||
|
||||
// Get user's data from the database
|
||||
newUser.updateUserInfo();
|
||||
|
||||
try {
|
||||
newUser.countryID = getCountryID(userCountryCode);
|
||||
|
||||
// We're ready to start putting together a login response
|
||||
|
||||
// The reply id is the user's id in any other case than an error in which case negative numbers are used
|
||||
osuPacketWriter.LoginReply(newUser.id);
|
||||
osuPacketWriter.ProtocolNegotiation(19);
|
||||
// Permission level 4 is osu!supporter
|
||||
osuPacketWriter.LoginPermissions(4);
|
||||
|
||||
// Set title screen image
|
||||
//osuPacketWriter.TitleUpdate("http://puu.sh/jh7t7/20c04029ad.png|https://osu.ppy.sh/news/123912240253");
|
||||
|
||||
// Add user panel data packets
|
||||
UserPresence(newUser, newUser.id);
|
||||
StatusUpdate(newUser, newUser.id);
|
||||
|
||||
// peppy pls, why
|
||||
osuPacketWriter.ChannelListingComplete();
|
||||
|
||||
// Setup chat
|
||||
shared.chatManager.ForceJoinChannels(newUser);
|
||||
shared.chatManager.SendChannelListing(newUser);
|
||||
|
||||
// Construct & send user's friends list
|
||||
const userFriends = await shared.database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]);
|
||||
const friendsArray:Array<number> = new Array<number>();
|
||||
for (let useFriend of userFriends) {
|
||||
const friendId:number = useFriend.friendsWith;
|
||||
friendsArray.push(friendId);
|
||||
|
||||
// Also fetch presence for friend if they are online
|
||||
if (shared.users.getById(friendId) === undefined) { continue; }
|
||||
|
||||
const friendPresence = UserPresence(shared, friendId);
|
||||
if (friendPresence === undefined) { continue; }
|
||||
|
||||
friendsPresence = Buffer.concat([
|
||||
friendsPresence,
|
||||
friendPresence
|
||||
], friendsPresence.length + friendPresence.length);
|
||||
}
|
||||
osuPacketWriter.FriendsList(friendsArray);
|
||||
|
||||
// After sending the user their friends list send them the online users
|
||||
UserPresenceBundle(newUser);
|
||||
|
||||
osuPacketWriter.Announce(`Welcome back ${loginInfo.username}!`);
|
||||
// TODO: Remove once merged into master
|
||||
osuPacketWriter.Announce("WARNING\nThis is a development test server made for the TypeScript rewrite.\nAnything could happen be it data loss, catastrophic crashes or otherwise.\nHere be dragons.");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
res.removeHeader('X-Powered-By');
|
||||
res.removeHeader('Date');
|
||||
// Complete / Fail login
|
||||
const writerBuffer:Buffer = osuPacketWriter.toBuffer;
|
||||
if (newUser === undefined) {
|
||||
res.writeHead(200, {
|
||||
"Connection": "keep-alive",
|
||||
"Keep-Alive": "timeout=5, max=100"
|
||||
});
|
||||
console.log(res.headersSent);
|
||||
switch (loginResult) {
|
||||
case LoginResult.INCORRECT:
|
||||
res.end(incorrectLoginResponse, () => {
|
||||
ConsoleHelper.printBancho(`User login failed (Incorrect Password) took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||
});
|
||||
break;
|
||||
case LoginResult.MIGRATION:
|
||||
res.end(requiredPWChangeResponse, () => {
|
||||
ConsoleHelper.printBancho(`User login failed (Migration Required) took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
"cho-token": newUser.uuid,
|
||||
"Connection": "keep-alive",
|
||||
"Keep-Alive": "timeout=5, max=100",
|
||||
});
|
||||
res.end(osuPacketWriter.toBuffer, () => {
|
||||
res.end(writerBuffer, () => {
|
||||
ConsoleHelper.printBancho(`User login finished, took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Channel } from "./objects/Channel";
|
||||
import { SharedContent } from "./interfaces/SharedContent";
|
||||
import { Shared } from "./objects/Shared";
|
||||
import { SlotStatus } from "./enums/SlotStatus";
|
||||
import { DataStream } from "./objects/DataStream";
|
||||
import { Match } from "./objects/Match";
|
||||
|
@ -10,18 +10,18 @@ import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
|||
import { MatchArray } from "./objects/MatchArray";
|
||||
import { MatchJoinData } from "./interfaces/MatchJoinData";
|
||||
import { MatchData } from "./interfaces/MatchData";
|
||||
const osu = require("osu-packet");
|
||||
import { osu } from "../osuTyping";
|
||||
|
||||
export class MultiplayerManager {
|
||||
private readonly sharedContent:SharedContent;
|
||||
private readonly shared:Shared;
|
||||
private matches:MatchArray = new MatchArray();
|
||||
private readonly lobbyStream:DataStream;
|
||||
private readonly lobbyChat:Channel;
|
||||
|
||||
public constructor(sharedContent:SharedContent) {
|
||||
this.sharedContent = sharedContent;
|
||||
this.lobbyStream = sharedContent.streams.CreateStream("multiplayer:lobby", false);
|
||||
const channel = this.sharedContent.chatManager.GetChannelByName("#lobby");
|
||||
public constructor(shared:Shared) {
|
||||
this.shared = shared;
|
||||
this.lobbyStream = shared.streams.CreateStream("multiplayer:lobby", false);
|
||||
const channel = this.shared.chatManager.GetChannelByName("#lobby");
|
||||
if (channel === undefined) {
|
||||
throw "Something has gone horribly wrong, the lobby channel does not exist!";
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ export class MultiplayerManager {
|
|||
match.matchStream.AddUser(user);
|
||||
match.matchChatChannel.Join(user);
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchJoinSuccess(match.generateMatchJSON());
|
||||
|
||||
|
@ -92,7 +92,7 @@ export class MultiplayerManager {
|
|||
|
||||
this.UpdateLobbyListing();
|
||||
} catch (e) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchJoinFail();
|
||||
|
||||
|
@ -107,8 +107,8 @@ export class MultiplayerManager {
|
|||
}
|
||||
|
||||
public GenerateLobbyListing(user?:User) : Buffer {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
let bufferToSend = UserPresenceBundle(this.sharedContent);
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
let bufferToSend = UserPresenceBundle(this.shared);
|
||||
|
||||
for (let match of this.matches.getIterableItems()) {
|
||||
for (let slot of match.slots) {
|
||||
|
@ -116,8 +116,13 @@ export class MultiplayerManager {
|
|||
continue;
|
||||
}
|
||||
|
||||
const presenceBuffer = UserPresence(this.sharedContent, slot.player.id);
|
||||
const statusBuffer = StatusUpdate(this.sharedContent, slot.player.id);
|
||||
const presenceBuffer = UserPresence(this.shared, slot.player.id);
|
||||
const statusBuffer = StatusUpdate(this.shared, slot.player.id);
|
||||
|
||||
if (presenceBuffer === undefined || statusBuffer === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bufferToSend = Buffer.concat([bufferToSend, presenceBuffer, statusBuffer], bufferToSend.length + presenceBuffer.length + statusBuffer.length);
|
||||
}
|
||||
|
||||
|
@ -134,8 +139,12 @@ export class MultiplayerManager {
|
|||
return bufferToSend;
|
||||
}
|
||||
|
||||
public GetMatchById(id:number) : Match | undefined {
|
||||
return this.matches.getById(id);
|
||||
}
|
||||
|
||||
public async CreateMatch(user:User, matchData:MatchData) {
|
||||
const match = await Match.createMatch(user, matchData, this.sharedContent);
|
||||
const match = await Match.createMatch(user, matchData, this.shared);
|
||||
this.matches.add(match.matchId.toString(), match);
|
||||
this.JoinMatch(user, match.matchId);
|
||||
}
|
||||
|
|
41
server/PrivateChatManager.ts
Normal file
41
server/PrivateChatManager.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Channel } from "./objects/Channel";
|
||||
import { ConsoleHelper } from "../ConsoleHelper";
|
||||
import { FunkyArray } from "./objects/FunkyArray";
|
||||
import { User } from "./objects/User";
|
||||
import { Shared } from "./objects/Shared";
|
||||
import { osu } from "../osuTyping";
|
||||
import { PrivateChannel } from "./objects/PrivateChannel";
|
||||
|
||||
export class PrivateChatManager {
|
||||
public chatChannels:FunkyArray<PrivateChannel> = new FunkyArray<PrivateChannel>();
|
||||
private readonly shared:Shared;
|
||||
|
||||
public constructor(shared:Shared) {
|
||||
this.shared = shared;
|
||||
}
|
||||
|
||||
public AddChannel(user0:User, user1:User) : PrivateChannel {
|
||||
const stream = this.shared.streams.CreateStream(`private_channel:${user0.username},${user1.username}`, true);
|
||||
const channel = new PrivateChannel(user0, user1, stream);
|
||||
this.chatChannels.add(channel.name, channel);
|
||||
ConsoleHelper.printChat(`Created private chat channel [${channel.name}]`);
|
||||
return channel;
|
||||
}
|
||||
|
||||
public RemoveChannel(channel:PrivateChannel | string) {
|
||||
if (channel instanceof Channel) {
|
||||
channel.stream.Delete();
|
||||
this.chatChannels.remove(channel.stream.name);
|
||||
} else {
|
||||
const chatChannel = this.GetChannelByName(channel);
|
||||
if (chatChannel instanceof Channel) {
|
||||
chatChannel.stream.Delete();
|
||||
this.chatChannels.remove(chatChannel.stream.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GetChannelByName(channelName:string) : PrivateChannel | undefined {
|
||||
return this.chatChannels.getByKey(channelName);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
import { DataStream } from "./objects/DataStream";
|
||||
import { SharedContent } from "./interfaces/SharedContent";
|
||||
import { Shared } from "./objects/Shared";
|
||||
import { User } from "./objects/User";
|
||||
const osu = require("osu-packet");
|
||||
import { osu } from "../osuTyping";
|
||||
|
||||
export class SpectatorManager {
|
||||
private sharedContent:SharedContent;
|
||||
private shared:Shared;
|
||||
|
||||
public constructor(sharedContent:SharedContent) {
|
||||
this.sharedContent = sharedContent;
|
||||
public constructor(shared:Shared) {
|
||||
this.shared = shared;
|
||||
}
|
||||
|
||||
public startSpectating(user:User, userIdToSpectate:number) {
|
||||
const userToSpectate = this.sharedContent.users.getById(userIdToSpectate);
|
||||
const userToSpectate = this.shared.users.getById(userIdToSpectate);
|
||||
if (userToSpectate === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -19,20 +19,20 @@ export class SpectatorManager {
|
|||
// Use existing or create spectator stream
|
||||
let spectateStream:DataStream;
|
||||
if (userToSpectate.spectatorStream === undefined) {
|
||||
user.spectatorStream = spectateStream = userToSpectate.spectatorStream = this.sharedContent.streams.CreateStream(`spectator:${userToSpectate.username}`);
|
||||
user.spectatorStream = spectateStream = userToSpectate.spectatorStream = this.shared.streams.CreateStream(`spectator:${userToSpectate.username}`);
|
||||
} else {
|
||||
user.spectatorStream = spectateStream = userToSpectate.spectatorStream;
|
||||
}
|
||||
|
||||
user.spectatingUser = userToSpectate;
|
||||
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
let osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.SpectatorJoined(user.id);
|
||||
|
||||
userToSpectate.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.FellowSpectatorJoined(user.id);
|
||||
|
||||
|
@ -45,7 +45,7 @@ export class SpectatorManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.SpectateFrames(spectateFrameData);
|
||||
|
||||
user.spectatorStream.Send(osuPacketWriter.toBuffer);
|
||||
|
@ -58,7 +58,7 @@ export class SpectatorManager {
|
|||
|
||||
const spectatedUser = user.spectatingUser;
|
||||
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
let osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.SpectatorLeft(user.id);
|
||||
spectatedUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
|
@ -69,7 +69,7 @@ export class SpectatorManager {
|
|||
user.spectatingUser = undefined;
|
||||
|
||||
if (stream.IsActive) {
|
||||
osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.FellowSpectatorLeft(user.id);
|
||||
stream.Send(osuPacketWriter.toBuffer);
|
||||
} else {
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
||||
function escapeRegExp(string:string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
export function replaceAll(inputString:string, toReplace:string, toReplaceWith:string) {
|
||||
return inputString.replace(`/:${escapeRegExp(toReplace)}:/g`, toReplaceWith);
|
||||
}
|
||||
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
export function generateSession() : Promise<string> {
|
||||
|
|
19
server/commands/BaseCommand.ts
Normal file
19
server/commands/BaseCommand.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { ICommand } from "../interfaces/ICommand";
|
||||
import { Channel } from "../objects/Channel";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { User } from "../objects/User";
|
||||
|
||||
export class BaseCommand implements ICommand {
|
||||
public shared:Shared;
|
||||
public readonly helpText:string = "No help page was found for that command";
|
||||
public readonly helpDescription:string = "Command has no description set";
|
||||
public readonly helpArguments:Array<string> = new Array<string>();
|
||||
|
||||
public constructor(shared:Shared) {
|
||||
this.shared = shared;
|
||||
}
|
||||
|
||||
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||
|
||||
}
|
||||
}
|
41
server/commands/Help.ts
Normal file
41
server/commands/Help.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Channel } from "../objects/Channel";
|
||||
import { User } from "../objects/User";
|
||||
import { BaseCommand } from "./BaseCommand";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { ICommand } from "../interfaces/ICommand";
|
||||
|
||||
export class HelpCommand extends BaseCommand {
|
||||
public readonly helpDescription:string = "Shows this message! :)";
|
||||
|
||||
private readonly commandList:{ [id:string]: ICommand };
|
||||
private commandKeys:Array<string> = new Array<string>();
|
||||
|
||||
public constructor(shared:Shared, commands:{ [id:string]: ICommand }) {
|
||||
super(shared);
|
||||
this.commandList = commands;
|
||||
}
|
||||
|
||||
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||
if (this.commandKeys.length === 0) {
|
||||
this.commandKeys = Object.keys(this.commandList);
|
||||
}
|
||||
|
||||
// All commands
|
||||
if (args.length === 0) {
|
||||
let constructedHelp = "Help:\n";
|
||||
for (let key of this.commandKeys) {
|
||||
constructedHelp += ` !${key} - ${this.commandList[key].helpDescription}\n`;
|
||||
}
|
||||
channel.SendBotMessage(constructedHelp.slice(0, constructedHelp.length - 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// Command page
|
||||
const commandName = args[0].toLowerCase();
|
||||
if (commandName in this.commandList) {
|
||||
channel.SendBotMessage(this.commandList[commandName].helpText);
|
||||
} else {
|
||||
channel.SendBotMessage("No help page was found for that command");
|
||||
}
|
||||
}
|
||||
}
|
17
server/commands/Lock.ts
Normal file
17
server/commands/Lock.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Channel } from "../objects/Channel";
|
||||
import { User } from "../objects/User";
|
||||
import { BaseCommand } from "./BaseCommand";
|
||||
|
||||
export 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"}`);
|
||||
}
|
||||
}
|
98
server/commands/Multiplayer.ts
Normal file
98
server/commands/Multiplayer.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
import { Channel } from "../objects/Channel";
|
||||
import { User } from "../objects/User";
|
||||
import { Match } from "../objects/Match";
|
||||
import { BaseCommand } from "./BaseCommand";
|
||||
|
||||
const helpText = `Multiplayer Subcommands:
|
||||
!mp start - Starts a multiplayer match with a delay (optional)
|
||||
!mp abort - Aborts the currently running round / countdown
|
||||
!mp obr - Toggles Battle Royale mode`;
|
||||
|
||||
export class MultiplayerCommands extends BaseCommand {
|
||||
public readonly helpText:string = helpText;
|
||||
public readonly helpDescription:string = "Command for use in multiplayer matches.";
|
||||
public readonly helpArguments:Array<string> = ["subCommand"];
|
||||
|
||||
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||
// TODO: Determine if this check is correct
|
||||
if (sender.match == undefined || channel.name != "#multiplayer") {
|
||||
channel.SendBotMessage("You must be in a multiplayer match to use this command");
|
||||
return;
|
||||
}
|
||||
// Check if sender is match host
|
||||
if (!User.Equals(sender, sender.match.host)) {
|
||||
channel.SendBotMessage("You must be the match host to use multiplayer commands");
|
||||
return;
|
||||
}
|
||||
if (args.length === 0) {
|
||||
channel.SendBotMessage("You must specify a sub command, use \"!help mp\" to see a list of them.");
|
||||
return;
|
||||
}
|
||||
|
||||
const subCommand = args[0].toLowerCase();
|
||||
args.shift();
|
||||
|
||||
switch (subCommand) {
|
||||
case "start":
|
||||
return mpStart(channel, sender.match, args);
|
||||
|
||||
case "abort":
|
||||
return mpAbort(channel, sender.match);
|
||||
|
||||
case "obr":
|
||||
return mpOBR(channel, sender.match);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mpStart(channel:Channel, match:Match, args:Array<string>) {
|
||||
// If no time is specified start instantly
|
||||
if (args.length === 0) {
|
||||
channel.SendBotMessage("Good luck, have fun!");
|
||||
setTimeout(() => match.startMatch(), 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
const countdownTime = parseInt(args[0]);
|
||||
if (isNaN(countdownTime)) {
|
||||
channel.SendBotMessage("Countdown time must be a valid number");
|
||||
return;
|
||||
}
|
||||
|
||||
let countdownUpdates = 0;
|
||||
match.countdownTime = countdownTime;
|
||||
match.countdownTimer = setInterval(() => {
|
||||
if (match.countdownTime <= 0) {
|
||||
clearInterval(match.countdownTimer);
|
||||
match.countdownTimer = undefined;
|
||||
channel.SendBotMessage("Good luck, have fun!");
|
||||
setTimeout(() => match.startMatch(), 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.countdownTime <= 5 && match.countdownTime > 0) {
|
||||
channel.SendBotMessage(`Starting in ${match.countdownTime} seconds`);
|
||||
} else if (match.countdownTime <= 30 ? countdownUpdates % 10 === 0 : countdownUpdates % 30 === 0) {
|
||||
channel.SendBotMessage(`Starting in ${match.countdownTime} seconds`);
|
||||
}
|
||||
|
||||
match.countdownTime--;
|
||||
countdownUpdates++;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function mpAbort(channel:Channel, match:Match) {
|
||||
if (match.countdownTimer && match.countdownTime > 0) {
|
||||
clearInterval(match.countdownTimer);
|
||||
match.countdownTimer = undefined;
|
||||
channel.SendBotMessage("Aborted countdown");
|
||||
} else {
|
||||
// TODO: Determine the correct way to abort a round
|
||||
match.finishMatch();
|
||||
channel.SendBotMessage("Aborted current round");
|
||||
}
|
||||
}
|
||||
|
||||
function mpOBR(channel:Channel, match:Match) {
|
||||
|
||||
}
|
37
server/commands/Ranking.ts
Normal file
37
server/commands/Ranking.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Channel } from "../objects/Channel";
|
||||
import { User } from "../objects/User";
|
||||
import { RankingModes } from "../enums/RankingModes";
|
||||
import { BaseCommand } from "./BaseCommand";
|
||||
|
||||
const helpText = `Ranking Modes:
|
||||
!ranking pp - Sets your ranking mode to pp
|
||||
!ranking score - Sets your ranking mode to score
|
||||
!ranking acc - Sets your ranking mode to accuracy`;
|
||||
|
||||
export class RankingCommand extends BaseCommand {
|
||||
public readonly helpText:string = helpText;
|
||||
public readonly helpDescription:string = "Sets your prefered ranking type";
|
||||
|
||||
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||
if (args.length === 0) {
|
||||
channel.SendBotMessage("You must specify a ranking mode, use \"!help ranking\" to see the options.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "pp":
|
||||
sender.rankingMode = RankingModes.PP;
|
||||
channel.SendBotMessage("Set ranking mode to pp.");
|
||||
break;
|
||||
case "score":
|
||||
sender.rankingMode = RankingModes.RANKED_SCORE;
|
||||
channel.SendBotMessage("Set ranking mode to score.");
|
||||
break;
|
||||
case "acc":
|
||||
sender.rankingMode = RankingModes.AVG_ACCURACY;
|
||||
channel.SendBotMessage("Set ranking mode to accuracy.");
|
||||
break;
|
||||
}
|
||||
sender.updateUserInfo(true);
|
||||
}
|
||||
}
|
21
server/commands/RollCommand.ts
Normal file
21
server/commands/RollCommand.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Channel } from "../objects/Channel";
|
||||
import { User } from "../objects/User";
|
||||
import { BaseCommand } from "./BaseCommand";
|
||||
|
||||
export class RollCommand extends BaseCommand {
|
||||
public readonly helpDescription:string = "Roll some dice and get a random number between 1 and a number (default 100)";
|
||||
public readonly helpArguments:Array<string> = ["number"];
|
||||
|
||||
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||
let limit = 99;
|
||||
if (args.length === 1) {
|
||||
const userLimit = parseInt(args[0]);
|
||||
if (!isNaN(userLimit)) {
|
||||
limit = userLimit;
|
||||
}
|
||||
}
|
||||
|
||||
const number = Math.round(Math.random() * limit) + 1;
|
||||
channel.SendBotMessage(`${sender.username} rolls ${number} point(s)`);
|
||||
}
|
||||
}
|
10
server/interfaces/ICommand.ts
Normal file
10
server/interfaces/ICommand.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Channel } from "../objects/Channel";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { User } from "../objects/User";
|
||||
|
||||
export interface ICommand {
|
||||
shared:Shared,
|
||||
helpText:string,
|
||||
helpDescription:string,
|
||||
exec: (channel:Channel, sender:User, args:Array<string>) => void
|
||||
}
|
4
server/interfaces/IpZxqResponse.ts
Normal file
4
server/interfaces/IpZxqResponse.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export interface IpZxqResponse {
|
||||
country: string,
|
||||
loc: string
|
||||
}
|
6
server/interfaces/MessageData.ts
Normal file
6
server/interfaces/MessageData.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface MessageData {
|
||||
sendingClient: string,
|
||||
message: string,
|
||||
target: string,
|
||||
senderId: number
|
||||
}
|
65
server/interfaces/OsuPacketWriter.ts
Normal file
65
server/interfaces/OsuPacketWriter.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { MatchData } from "./MatchData"
|
||||
import { MessageData } from "./MessageData"
|
||||
|
||||
export interface OsuPacketWriter {
|
||||
// Functions
|
||||
LoginReply(data:number) : OsuPacketWriter,
|
||||
CommandError() : OsuPacketWriter,
|
||||
SendMessage(data:MessageData) : OsuPacketWriter,
|
||||
Ping() : OsuPacketWriter,
|
||||
HandleIrcChangeUsername(data:any) : OsuPacketWriter,
|
||||
HandleIrcQuit() : OsuPacketWriter,
|
||||
HandleOsuUpdate(data:any) : OsuPacketWriter,
|
||||
HandleUserQuit(data:any) : OsuPacketWriter,
|
||||
SpectatorJoined(data:any) : OsuPacketWriter,
|
||||
SpectatorLeft(data:any) : OsuPacketWriter,
|
||||
SpectateFrames(data:any) : OsuPacketWriter,
|
||||
VersionUpdate() : OsuPacketWriter,
|
||||
SpectatorCantSpectate(data:any) : OsuPacketWriter,
|
||||
GetAttention() : OsuPacketWriter,
|
||||
Announce(data:string) : OsuPacketWriter,
|
||||
MatchUpdate(data:MatchData) : OsuPacketWriter,
|
||||
MatchNew(data:any) : OsuPacketWriter,
|
||||
MatchDisband(data:any) : OsuPacketWriter,
|
||||
MatchJoinSuccess(data:any) : OsuPacketWriter,
|
||||
MatchJoinFail() : OsuPacketWriter,
|
||||
FellowSpectatorJoined(data:any) : OsuPacketWriter,
|
||||
FellowSpectatorLeft(data:any) : OsuPacketWriter,
|
||||
MatchStart(data:any) : OsuPacketWriter,
|
||||
MatchScoreUpdate(data:any) : OsuPacketWriter,
|
||||
MatchTransferHost(data:any) : OsuPacketWriter,
|
||||
MatchAllPlayersLoaded() : OsuPacketWriter,
|
||||
MatchPlayerFailed(data:any) : OsuPacketWriter,
|
||||
MatchComplete() : OsuPacketWriter,
|
||||
MatchSkip() : OsuPacketWriter,
|
||||
Unauthorised() : OsuPacketWriter,
|
||||
ChannelJoinSuccess(data:any) : OsuPacketWriter,
|
||||
ChannelAvailable(data:any) : OsuPacketWriter,
|
||||
ChannelRevoked(data:any) : OsuPacketWriter,
|
||||
ChannelAvailableAutojoin(data:any) : OsuPacketWriter,
|
||||
BeatmapInfoReply() : OsuPacketWriter,
|
||||
LoginPermissions(data:number) : OsuPacketWriter,
|
||||
FriendsList(data:Array<number>) : OsuPacketWriter,
|
||||
ProtocolNegotiation(data:number) : OsuPacketWriter,
|
||||
TitleUpdate(data:string) : OsuPacketWriter,
|
||||
Monitor() : OsuPacketWriter,
|
||||
MatchPlayerSkipped(data:any) : OsuPacketWriter,
|
||||
UserPresence(data:any) : OsuPacketWriter,
|
||||
Restart(data:any) : OsuPacketWriter,
|
||||
Invite(data:any) : OsuPacketWriter,
|
||||
ChannelListingComplete() : OsuPacketWriter,
|
||||
MatchChangePassword(data:any) : OsuPacketWriter,
|
||||
BanInfo(data:any) : OsuPacketWriter,
|
||||
UserSilenced(data:any) : OsuPacketWriter,
|
||||
UserPresenceSingle(data:any) : OsuPacketWriter,
|
||||
UserPresenceBundle(data:any) : OsuPacketWriter,
|
||||
UserPMBlocked(data:any) : OsuPacketWriter,
|
||||
TargetIsSilenced(data:any) : OsuPacketWriter,
|
||||
VersionUpdateForced() : OsuPacketWriter,
|
||||
SwitchServer(data:any) : OsuPacketWriter,
|
||||
AccountRestricted() : OsuPacketWriter,
|
||||
RTX(data:any) : OsuPacketWriter,
|
||||
SwitchTourneyServer(data:any) : OsuPacketWriter
|
||||
|
||||
toBuffer : Buffer
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { ChatManager } from "../ChatManager";
|
||||
import { MultiplayerManager } from "../MultiplayerManager";
|
||||
import { Database } from "../objects/Database";
|
||||
import { DataStreamArray } from "../objects/DataStreamArray";
|
||||
import { UserArray } from "../objects/UserArray";
|
||||
|
||||
export interface SharedContent {
|
||||
chatManager:ChatManager,
|
||||
database:Database,
|
||||
mutiplayerManager:MultiplayerManager,
|
||||
streams:DataStreamArray,
|
||||
users:UserArray,
|
||||
}
|
|
@ -1,77 +1,61 @@
|
|||
import { SharedContent } from "../interfaces/SharedContent";
|
||||
import { osu } from "../../osuTyping";
|
||||
import { Bot } from "../Bot";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { DataStream } from "./DataStream";
|
||||
import { User } from "./User";
|
||||
const osu = require("osu-packet");
|
||||
|
||||
export class Channel {
|
||||
public name:string;
|
||||
public description:string;
|
||||
public stream:DataStream;
|
||||
private isLocked:boolean = false;
|
||||
public isLocked:boolean = false;
|
||||
private _isSpecial:boolean = false;
|
||||
|
||||
private readonly botUser:User;
|
||||
private readonly bot:Bot;
|
||||
|
||||
public constructor(sharedContent:SharedContent, name:string, description:string, stream:DataStream, isSpecial:boolean = false) {
|
||||
public constructor(shared:Shared, name:string, description:string, stream:DataStream, isSpecial:boolean = false) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.stream = stream;
|
||||
this._isSpecial = isSpecial;
|
||||
|
||||
const bot = sharedContent.users.getByKey("bot");
|
||||
if (!(bot instanceof User)) {
|
||||
throw "Something has gone horribly wrong, the bot user doesn't exist!";
|
||||
}
|
||||
this.botUser = bot;
|
||||
this.bot = shared.bot;
|
||||
}
|
||||
|
||||
public get self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public get isSpecial() {
|
||||
public get isSpecial() : boolean {
|
||||
return this._isSpecial;
|
||||
}
|
||||
|
||||
public get userCount() {
|
||||
public get userCount() : number {
|
||||
return this.stream.userCount;
|
||||
}
|
||||
|
||||
public SendMessage(sender:User, message:string) {
|
||||
const isBotCommand = message[0] === "!";
|
||||
if (!this.isLocked) {
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: sender.username,
|
||||
message: message,
|
||||
target: this.name,
|
||||
senderId: sender.id
|
||||
});
|
||||
this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender);
|
||||
}
|
||||
|
||||
if (this.isLocked && !isBotCommand) {
|
||||
if (message[0] === "!") {
|
||||
this.bot.OnMessage(this, sender, message);
|
||||
} else if (this.isLocked) {
|
||||
return this.SendSystemMessage("This channel is currently locked", sender);
|
||||
}
|
||||
|
||||
if (isBotCommand) {
|
||||
if (message.split(" ")[0] === "!lock") {
|
||||
this.isLocked = true;
|
||||
}
|
||||
|
||||
if (message === "!mp start") {
|
||||
this.SendBotMessage("glhf!");
|
||||
sender.match?.startMatch();
|
||||
}
|
||||
}
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: sender.username,
|
||||
message: message,
|
||||
target: this.name,
|
||||
senderId: sender.id
|
||||
});
|
||||
this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender);
|
||||
}
|
||||
|
||||
public SendBotMessage(message:string, sendTo?:User) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: this.botUser.username,
|
||||
sendingClient: this.bot.user.username,
|
||||
message: message,
|
||||
target: this.name,
|
||||
senderId: this.botUser.id
|
||||
senderId: this.bot.user.id
|
||||
});
|
||||
|
||||
if (sendTo instanceof User) {
|
||||
|
@ -82,7 +66,7 @@ export class Channel {
|
|||
}
|
||||
|
||||
public SendSystemMessage(message:string, sendTo?:User) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: "System",
|
||||
message: message,
|
||||
|
@ -99,12 +83,15 @@ export class Channel {
|
|||
|
||||
public Join(user:User) {
|
||||
this.stream.AddUser(user);
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.ChannelJoinSuccess(this.name);
|
||||
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
|
||||
public Leave(user:User) {
|
||||
this.stream.RemoveUser(user);
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.ChannelRevoked(this.name);
|
||||
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
}
|
|
@ -5,12 +5,15 @@ import { User } from "./User";
|
|||
import { UserArray } from "./UserArray";
|
||||
import { hexlify } from "../Util";
|
||||
|
||||
type DeleteFunction = (dataStream:DataStream) => void;
|
||||
|
||||
export class DataStream {
|
||||
private users:UserArray = new UserArray();
|
||||
public readonly name:string;
|
||||
private readonly parent:DataStreamArray;
|
||||
private readonly removeWhenEmpty:boolean;
|
||||
private inactive:boolean = false;
|
||||
public onDelete?:DeleteFunction;
|
||||
|
||||
public constructor(name:string, parent:DataStreamArray, removeWhenEmpty:boolean) {
|
||||
this.name = name;
|
||||
|
@ -32,6 +35,10 @@ export class DataStream {
|
|||
return this.users.getLength();
|
||||
}
|
||||
|
||||
public HasUser(user:User) : boolean {
|
||||
return this.users.getByKey(user.uuid) !== undefined;
|
||||
}
|
||||
|
||||
public AddUser(user:User) : void {
|
||||
this.checkInactive();
|
||||
|
||||
|
@ -54,6 +61,9 @@ export class DataStream {
|
|||
}
|
||||
|
||||
public Delete() {
|
||||
if (typeof(this.onDelete) === "function") {
|
||||
this.onDelete(this);
|
||||
}
|
||||
this.parent.DeleteStream(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ export class Database {
|
|||
|
||||
public connected:boolean = false;
|
||||
|
||||
public constructor(databaseAddress:string, databasePort:number = 3306, databaseUsername:string, databasePassword:string, databaseName:string, connectedCallback:Function) {
|
||||
public constructor(databaseAddress:string, databasePort:number = 3306, databaseUsername:string, databasePassword:string, databaseName:string) {
|
||||
this.connectionPool = createPool({
|
||||
connectionLimit: Database.CONNECTION_LIMIT,
|
||||
host: databaseAddress,
|
||||
|
@ -17,27 +17,7 @@ export class Database {
|
|||
database: databaseName
|
||||
});
|
||||
|
||||
const classCreationTime:number = Date.now();
|
||||
let lastQueryFinished = true;
|
||||
const connectionCheckInterval = setInterval(() => {
|
||||
if (lastQueryFinished) {
|
||||
lastQueryFinished = false;
|
||||
this.query("SELECT name FROM osu_info LIMIT 1")
|
||||
.then(data => {
|
||||
if (!this.connected) {
|
||||
this.connected = true;
|
||||
ConsoleHelper.printInfo(`Connected to database. Took ${Date.now() - classCreationTime}ms`);
|
||||
clearInterval(connectionCheckInterval);
|
||||
lastQueryFinished = true;
|
||||
|
||||
connectedCallback();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
lastQueryFinished = true;
|
||||
});
|
||||
}
|
||||
}, 16);
|
||||
ConsoleHelper.printInfo(`Connected DB connection pool. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
|
||||
}
|
||||
|
||||
public query(query = "", data?:Array<any>) {
|
||||
|
|
|
@ -19,6 +19,8 @@ export class LoginInfo {
|
|||
data = data.toString();
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
|
||||
const loginData:Array<string> = data.split("\n");
|
||||
const extraData:Array<string> = loginData[2].split("|");
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Channel } from "./Channel";
|
||||
import { SharedContent } from "../interfaces/SharedContent";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { DataStream } from "./DataStream";
|
||||
import { Slot } from "./Slot";
|
||||
import { User } from "./User";
|
||||
|
@ -11,8 +11,7 @@ import { MatchStartSkipData } from "../interfaces/MatchStartSkipData";
|
|||
import { Mods } from "../enums/Mods";
|
||||
import { PlayerScore } from "../interfaces/PlayerScore";
|
||||
import { MatchScoreData } from "../interfaces/MatchScoreData";
|
||||
|
||||
const osu = require("osu-packet");
|
||||
import { osu } from "../../osuTyping";
|
||||
|
||||
export class Match {
|
||||
// osu! Data
|
||||
|
@ -44,11 +43,14 @@ export class Match {
|
|||
|
||||
public playerScores?:Array<PlayerScore>;
|
||||
|
||||
private cachedMatchJSON:MatchData;
|
||||
private readonly sharedContent:SharedContent;
|
||||
public countdownTime:number = 0;
|
||||
public countdownTimer?:NodeJS.Timeout;
|
||||
|
||||
private constructor(matchData:MatchData, sharedContent:SharedContent) {
|
||||
this.sharedContent = sharedContent;
|
||||
private cachedMatchJSON:MatchData;
|
||||
private readonly shared:Shared;
|
||||
|
||||
private constructor(matchData:MatchData, shared:Shared) {
|
||||
this.shared = shared;
|
||||
this.matchId = matchData.matchId;
|
||||
|
||||
this.inProgress = matchData.inProgress;
|
||||
|
@ -70,11 +72,11 @@ export class Match {
|
|||
if (slot.playerId === -1) {
|
||||
this.slots.push(new Slot(i, slot.status, slot.team, undefined, slot.mods));
|
||||
} else {
|
||||
this.slots.push(new Slot(i, slot.status, slot.team, sharedContent.users.getById(slot.playerId), slot.mods));
|
||||
this.slots.push(new Slot(i, slot.status, slot.team, shared.users.getById(slot.playerId), slot.mods));
|
||||
}
|
||||
}
|
||||
|
||||
const hostUser = sharedContent.users.getById(matchData.host);
|
||||
const hostUser = shared.users.getById(matchData.host);
|
||||
if (hostUser === undefined) {
|
||||
// NOTE: This should never be possible to hit
|
||||
// since this user JUST made the match.
|
||||
|
@ -90,39 +92,37 @@ export class Match {
|
|||
|
||||
this.seed = matchData.seed;
|
||||
|
||||
this.matchStream = sharedContent.streams.CreateStream(`multiplayer:match_${this.matchId}`, false);
|
||||
this.matchChatChannel = sharedContent.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`);
|
||||
this.matchStream = shared.streams.CreateStream(`multiplayer:match_${this.matchId}`, false);
|
||||
this.matchChatChannel = shared.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`);
|
||||
|
||||
this.cachedMatchJSON = matchData;
|
||||
|
||||
//this.playerScores = null;
|
||||
|
||||
//this.multiplayerExtras = null;
|
||||
|
||||
//this.isTourneyMatch = false;
|
||||
//this.tourneyClientUsers = [];
|
||||
}
|
||||
|
||||
public static createMatch(matchHost:User, matchData:MatchData, sharedContent:SharedContent) : Promise<Match> {
|
||||
public static createMatch(matchHost:User, matchData:MatchData, shared:Shared) : Promise<Match> {
|
||||
return new Promise<Match>(async (resolve, reject) => {
|
||||
try {
|
||||
matchData.matchId = (await sharedContent.database.query(
|
||||
matchData.matchId = (await shared.database.query(
|
||||
"INSERT INTO mp_matches (id, name, open_time, close_time, seed) VALUES (NULL, ?, UNIX_TIMESTAMP(), NULL, ?) RETURNING id;",
|
||||
[matchData.gameName, matchData.seed]
|
||||
))[0]["id"];
|
||||
|
||||
const matchInstance = new Match(matchData, sharedContent);
|
||||
const matchInstance = new Match(matchData, shared);
|
||||
|
||||
// Update the status of the current user
|
||||
StatusUpdate(matchHost, matchHost.id);
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchNew(matchInstance.generateMatchJSON());
|
||||
|
||||
matchHost.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||
shared.multiplayerManager.UpdateLobbyListing();
|
||||
|
||||
resolve(matchInstance);
|
||||
} catch (e) {
|
||||
|
@ -166,7 +166,7 @@ export class Match {
|
|||
this.matchStream.RemoveUser(user);
|
||||
this.matchChatChannel.Leave(user);
|
||||
|
||||
// Send this after removing the user from match streams to avoid a leave notification for self
|
||||
// Send this after removing the user from match streams to avoid a leave notification for self?
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,7 @@ export class Match {
|
|||
this.beatmapChecksum = this.cachedMatchJSON.beatmapChecksum = matchData.beatmapChecksum;
|
||||
|
||||
if (matchData.host !== this.host.id) {
|
||||
const hostUser = this.sharedContent.users.getById(matchData.host);
|
||||
const hostUser = this.shared.users.getById(matchData.host);
|
||||
if (hostUser === undefined) {
|
||||
// NOTE: This should never be possible to hit
|
||||
throw "Host User of match was undefined";
|
||||
|
@ -220,22 +220,24 @@ export class Match {
|
|||
}
|
||||
queryData.push(this.matchId);
|
||||
|
||||
await this.sharedContent.database.query(`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, queryData);
|
||||
await this.shared.database.query(`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, queryData);
|
||||
}
|
||||
|
||||
this.sendMatchUpdate();
|
||||
|
||||
// Update the match listing in the lobby to reflect these changes
|
||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||
}
|
||||
|
||||
public sendMatchUpdate() {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchUpdate(this.generateMatchJSON());
|
||||
|
||||
// Update all users in the match with new match information
|
||||
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||
|
||||
console.log(this.slots);
|
||||
}
|
||||
|
||||
public moveToSlot(user:User, slotToMoveTo:number) {
|
||||
|
@ -252,7 +254,7 @@ export class Match {
|
|||
|
||||
this.sendMatchUpdate();
|
||||
|
||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||
}
|
||||
|
||||
public changeTeam(user:User) {
|
||||
|
@ -325,7 +327,7 @@ export class Match {
|
|||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||
}
|
||||
|
||||
public missingBeatmap(user:User) {
|
||||
|
@ -383,7 +385,7 @@ export class Match {
|
|||
|
||||
// All players have finished playing, finish the match
|
||||
if (allSkipped) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchPlayerSkipped(user.id);
|
||||
osuPacketWriter.MatchSkip();
|
||||
|
@ -392,7 +394,7 @@ export class Match {
|
|||
|
||||
this.matchSkippedSlots = undefined;
|
||||
} else {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchPlayerSkipped(user.id);
|
||||
|
||||
|
@ -433,7 +435,7 @@ export class Match {
|
|||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||
}
|
||||
|
||||
startMatch() {
|
||||
|
@ -446,6 +448,7 @@ export class Match {
|
|||
|
||||
this.matchLoadSlots = new Array<MatchStartSkipData>();
|
||||
// Loop through all slots in the match
|
||||
console.log(this.slots);
|
||||
for (let slot of this.slots) {
|
||||
// Make sure the slot has a user in it
|
||||
if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) {
|
||||
|
@ -462,7 +465,7 @@ export class Match {
|
|||
slot.status = 32;
|
||||
}
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchStart(this.generateMatchJSON());
|
||||
|
||||
|
@ -473,7 +476,7 @@ export class Match {
|
|||
this.sendMatchUpdate();
|
||||
|
||||
// Update match listing in lobby to show the game is in progress
|
||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||
}
|
||||
|
||||
public matchPlayerLoaded(user:User) {
|
||||
|
@ -494,7 +497,7 @@ export class Match {
|
|||
|
||||
// All players have loaded the beatmap, start playing.
|
||||
if (allLoaded) {
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
let osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.MatchAllPlayersLoaded();
|
||||
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||
|
||||
|
@ -563,7 +566,7 @@ export class Match {
|
|||
this.matchLoadSlots = undefined;
|
||||
this.inProgress = false;
|
||||
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
let osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
let queryData:Array<any> = [
|
||||
this.matchId,
|
||||
|
@ -599,7 +602,7 @@ export class Match {
|
|||
slot.status = SlotStatus.NotReady;
|
||||
}
|
||||
|
||||
await this.sharedContent.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.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);
|
||||
|
||||
osuPacketWriter.MatchComplete();
|
||||
|
||||
|
@ -608,7 +611,7 @@ export class Match {
|
|||
// Update all users in the match with new info
|
||||
this.sendMatchUpdate();
|
||||
|
||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||
|
||||
// TODO: Re-implement multiplayer extras
|
||||
//if (this.multiplayerExtras != null) this.multiplayerExtras.onMatchFinished(JSON.parse(JSON.stringify(this.playerScores)));
|
||||
|
@ -617,7 +620,7 @@ export class Match {
|
|||
}
|
||||
|
||||
updatePlayerScore(user:User, matchScoreData:MatchScoreData) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
if (user.matchSlot === undefined || user.matchSlot.player === undefined || this.playerScores === undefined) {
|
||||
return;
|
||||
|
@ -647,7 +650,7 @@ export class Match {
|
|||
}
|
||||
|
||||
matchFailed(user:User) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
|
||||
// Make sure the user is in the match in a valid slot
|
||||
if (user.matchSlot === undefined) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Match } from "./Match";
|
|||
export class MatchArray extends FunkyArray<Match> {
|
||||
public getById(id:number) : Match | undefined {
|
||||
for (let match of this.getIterableItems()) {
|
||||
if (match.matchId == id) {
|
||||
if (match.matchId === id) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
|
61
server/objects/PrivateChannel.ts
Normal file
61
server/objects/PrivateChannel.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { osu } from "../../osuTyping";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { Channel } from "./Channel";
|
||||
import { DataStream } from "./DataStream";
|
||||
import { User } from "./User";
|
||||
|
||||
export class PrivateChannel extends Channel {
|
||||
private readonly user0:User;
|
||||
private readonly user1:User;
|
||||
|
||||
public constructor(user0:User, user1:User, stream:DataStream) {
|
||||
super(user0.shared, `${user0.username}${user1.username}`, "", stream);
|
||||
this.user0 = user0;
|
||||
this.user1 = user1;
|
||||
}
|
||||
|
||||
public override SendMessage(sender:User, message:string) {
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
if (!this.stream.HasUser(this.user0)) {
|
||||
this.Join(this.user0);
|
||||
}
|
||||
if (!this.stream.HasUser(this.user1)) {
|
||||
this.Join(this.user1);
|
||||
}
|
||||
|
||||
let target:string = this.user1.username;
|
||||
if (sender.uuid === this.user1.uuid) {
|
||||
target = this.user0.username;
|
||||
}
|
||||
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: sender.username,
|
||||
message: message,
|
||||
target: target,
|
||||
senderId: sender.id
|
||||
});
|
||||
this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender);
|
||||
}
|
||||
|
||||
public override Join(user:User) {
|
||||
this.stream.AddUser(user);
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
if (user.uuid === this.user0.uuid) {
|
||||
osuPacketWriter.ChannelJoinSuccess(this.user1.username);
|
||||
} else if (user.uuid === this.user1.uuid) {
|
||||
osuPacketWriter.ChannelJoinSuccess(this.user0.username);
|
||||
}
|
||||
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
|
||||
public override Leave(user:User) {
|
||||
this.stream.RemoveUser(user);
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
if (user.id === this.user0.id) {
|
||||
osuPacketWriter.ChannelRevoked(this.user1.username);
|
||||
} else if (user.id === this.user1.id) {
|
||||
osuPacketWriter.ChannelRevoked(this.user0.username);
|
||||
}
|
||||
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
}
|
44
server/objects/Shared.ts
Normal file
44
server/objects/Shared.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { ChatManager } from "../ChatManager";
|
||||
import { Config } from "../interfaces/Config";
|
||||
import { Database } from "../objects/Database";
|
||||
import { DataStreamArray } from "../objects/DataStreamArray";
|
||||
import { MultiplayerManager } from "../MultiplayerManager";
|
||||
import { PrivateChatManager } from "../PrivateChatManager";
|
||||
import { readFileSync } from "fs";
|
||||
import { UserArray } from "../objects/UserArray";
|
||||
import { User } from "./User";
|
||||
import { LatLng } from "./LatLng";
|
||||
import { Bot } from "../Bot";
|
||||
|
||||
export class Shared {
|
||||
public readonly chatManager:ChatManager;
|
||||
public readonly config:Config;
|
||||
public readonly database:Database;
|
||||
public readonly multiplayerManager:MultiplayerManager;
|
||||
public readonly privateChatManager:PrivateChatManager;
|
||||
public readonly streams:DataStreamArray;
|
||||
public readonly users:UserArray;
|
||||
public readonly bot:Bot;
|
||||
|
||||
public constructor() {
|
||||
this.config = JSON.parse(readFileSync("./config.json").toString()) as Config;
|
||||
this.database = new Database(this.config.database.address, this.config.database.port, this.config.database.username, this.config.database.password, this.config.database.name);
|
||||
this.streams = new DataStreamArray();
|
||||
|
||||
// Add the bot user
|
||||
this.users = new UserArray();
|
||||
const botUser = this.users.add("bot", new User(3, "SillyBot", "bot", this));
|
||||
botUser.location = new LatLng(50, -32);
|
||||
this.bot = new Bot(this, botUser);
|
||||
|
||||
this.chatManager = new ChatManager(this);
|
||||
// Setup chat channels
|
||||
this.chatManager.AddChatChannel("osu", "The main channel", true);
|
||||
this.chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
|
||||
this.chatManager.AddChatChannel("english", "Talk in exclusively English");
|
||||
this.chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese");
|
||||
|
||||
this.multiplayerManager = new MultiplayerManager(this);
|
||||
this.privateChatManager = new PrivateChatManager(this);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import { RankingModes } from "../enums/RankingModes";
|
|||
import { Match } from "./Match";
|
||||
import { DataStream } from "./DataStream";
|
||||
import { StatusUpdate } from "../packets/StatusUpdate";
|
||||
import { SharedContent } from "../interfaces/SharedContent";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { Slot } from "./Slot";
|
||||
|
||||
const rankingModes = [
|
||||
|
@ -15,7 +15,7 @@ const rankingModes = [
|
|||
export class User {
|
||||
private static readonly EMPTY_BUFFER = Buffer.alloc(0);
|
||||
|
||||
public sharedContent:SharedContent;
|
||||
public shared:Shared;
|
||||
|
||||
public id:number;
|
||||
public username:string;
|
||||
|
@ -65,12 +65,12 @@ export class User {
|
|||
return user0.uuid === user1.uuid;
|
||||
}
|
||||
|
||||
public constructor(id:number, username:string, uuid:string, sharedContent:SharedContent) {
|
||||
public constructor(id:number, username:string, uuid:string, shared:Shared) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.uuid = uuid;
|
||||
|
||||
this.sharedContent = sharedContent;
|
||||
this.shared = shared;
|
||||
}
|
||||
|
||||
// Concats new actions to the user's queue
|
||||
|
@ -83,7 +83,7 @@ export class User {
|
|||
}
|
||||
|
||||
// Updates the user's current action
|
||||
updatePresence(action:any) : void {
|
||||
updatePresence(action:any) {
|
||||
this.actionID = action.status;
|
||||
this.actionText = action.statusText;
|
||||
this.beatmapChecksum = action.beatmapChecksum;
|
||||
|
@ -97,10 +97,10 @@ export class User {
|
|||
}
|
||||
|
||||
// Gets the user's score information from the database and caches it
|
||||
async updateUserInfo(forceUpdate:boolean = false) : Promise<void> {
|
||||
const userScoreDB = await this.sharedContent.database.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
|
||||
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 mappedRankingMode = rankingModes[this.rankingMode];
|
||||
const userRankDB = await this.sharedContent.database.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);
|
||||
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";
|
||||
|
||||
|
|
23
server/objects/database/UserInfo.ts
Normal file
23
server/objects/database/UserInfo.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
export class UserInfo {
|
||||
id:number = Number.MIN_VALUE;
|
||||
username:string = "";
|
||||
username_safe:string = "";
|
||||
password_hash:string = "";
|
||||
password_salt:string = "";
|
||||
email:string = "";
|
||||
country:string = "";
|
||||
reg_date:Date = new Date(0);
|
||||
last_login_date:Date = new Date(0);
|
||||
last_played_mode:number = Number.MIN_VALUE;
|
||||
online_now:boolean = false;
|
||||
tags:number = Number.MIN_VALUE;
|
||||
supporter:boolean = false;
|
||||
web_session:string = "";
|
||||
verification_needed:boolean = false;
|
||||
password_change_required:boolean = false;
|
||||
has_old_password:number = Number.MIN_VALUE;
|
||||
password_reset_key:string = "";
|
||||
away_message:string = "";
|
||||
last_modified_time:Date = new Date(0);
|
||||
is_deleted:boolean = false;
|
||||
}
|
7
server/packets/AddFriend.ts
Normal file
7
server/packets/AddFriend.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { User } from "../objects/User";
|
||||
|
||||
export function AddFriend(user:User, friendId:number) {
|
||||
user.shared.database.query("INSERT INTO friends (user, friendsWith) VALUES (?, ?)", [
|
||||
user.id, friendId
|
||||
]);
|
||||
}
|
|
@ -5,7 +5,9 @@ export function ChangeAction(user:User, data:any) {
|
|||
user.updatePresence(data);
|
||||
|
||||
if (user.spectatorStream != null) {
|
||||
const statusUpdate = StatusUpdate(user.sharedContent, user.id);
|
||||
user.spectatorStream.Send(statusUpdate);
|
||||
const statusUpdate = StatusUpdate(user.shared, user.id);
|
||||
if (statusUpdate instanceof Buffer) {
|
||||
user.spectatorStream.Send(statusUpdate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,12 +8,12 @@ export async function Logout(user:User) {
|
|||
|
||||
const logoutStartTime = Date.now();
|
||||
|
||||
user.sharedContent.streams.RemoveUserFromAllStreams(user);
|
||||
user.shared.streams.RemoveUserFromAllStreams(user);
|
||||
|
||||
// Remove user from user list
|
||||
user.sharedContent.users.remove(user.uuid);
|
||||
user.shared.users.remove(user.uuid);
|
||||
|
||||
await user.sharedContent.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [user.sharedContent.users.getLength() - 1]);
|
||||
await user.shared.database.query("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}]`);
|
||||
}
|
27
server/packets/PrivateMessage.ts
Normal file
27
server/packets/PrivateMessage.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { MessageData } from "../interfaces/MessageData";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { PrivateChannel } from "../objects/PrivateChannel";
|
||||
import { User } from "../objects/User";
|
||||
|
||||
export function PrivateMessage(user:User, message:MessageData) {
|
||||
const shared:Shared = user.shared;
|
||||
const sendingTo = shared.users.getByUsername(message.target);
|
||||
if (!(sendingTo instanceof User)) {
|
||||
console.log("Sending User invalid");
|
||||
return;
|
||||
}
|
||||
let channel = shared.privateChatManager.GetChannelByName(`${user.username}${sendingTo.username}`);
|
||||
if (!(channel instanceof PrivateChannel)) {
|
||||
console.log("First find failed");
|
||||
// Try it the other way around
|
||||
channel = shared.privateChatManager.GetChannelByName(`${sendingTo.username}${user.username}`);
|
||||
}
|
||||
|
||||
if (!(channel instanceof PrivateChannel)) {
|
||||
console.log("Second find failed, creating");
|
||||
channel = shared.privateChatManager.AddChannel(user, sendingTo);
|
||||
}
|
||||
|
||||
console.log("sending");
|
||||
channel.SendMessage(user, message.message);
|
||||
}
|
7
server/packets/RemoveFriend.ts
Normal file
7
server/packets/RemoveFriend.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { User } from "../objects/User";
|
||||
|
||||
export function RemoveFriend(user:User, friendId:number) {
|
||||
user.shared.database.query("DELETE FROM friends WHERE user = ? AND friendsWith = ? LIMIT 1", [
|
||||
user.id, friendId
|
||||
]);
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
import { SharedContent } from "../interfaces/SharedContent";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { RankingModes } from "../enums/RankingModes";
|
||||
import { User } from "../objects/User";
|
||||
const osu = require("osu-packet");
|
||||
import { osu } from "../../osuTyping";
|
||||
|
||||
export function StatusUpdate(arg0:User | SharedContent, id:number) {
|
||||
export function StatusUpdate(arg0:User | Shared, id:number) {
|
||||
if (id == 3) return; // Ignore Bot
|
||||
|
||||
// Create new osu packet writer
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
let sharedContent:SharedContent;
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
let shared:Shared;
|
||||
if (arg0 instanceof User) {
|
||||
sharedContent = arg0.sharedContent;
|
||||
shared = arg0.shared;
|
||||
} else {
|
||||
sharedContent = arg0;
|
||||
shared = arg0;
|
||||
}
|
||||
|
||||
// Get user's class
|
||||
const userData = sharedContent.users.getById(id);
|
||||
const userData = shared.users.getById(id);
|
||||
|
||||
if (userData == null) return;
|
||||
|
||||
|
|
11
server/packets/TourneyJoinMatchChannel.ts
Normal file
11
server/packets/TourneyJoinMatchChannel.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { osu } from "../../osuTyping";
|
||||
import { User } from "../objects/User";
|
||||
|
||||
export function TourneyMatchJoinChannel(user:User, matchId:number) {
|
||||
const match = user.shared.multiplayerManager.GetMatchById(matchId);
|
||||
if (match === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
match.matchChatChannel.Join(user);
|
||||
}
|
10
server/packets/TourneyMatchLeaveChannel.ts
Normal file
10
server/packets/TourneyMatchLeaveChannel.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { User } from "../objects/User";
|
||||
|
||||
export function TourneyMatchLeaveChannel(user:User, matchId:number) {
|
||||
const match = user.shared.multiplayerManager.GetMatchById(matchId);
|
||||
if (match === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
match.matchChatChannel.Leave(user);
|
||||
}
|
31
server/packets/TourneyMatchSpecialInfo.ts
Normal file
31
server/packets/TourneyMatchSpecialInfo.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { osu } from "../../osuTyping";
|
||||
import { Match } from "../objects/Match";
|
||||
import { User } from "../objects/User";
|
||||
import { StatusUpdate } from "./StatusUpdate";
|
||||
import { UserPresence } from "./UserPresence";
|
||||
|
||||
export function TourneyMatchSpecialInfo(user:User, matchId:number) {
|
||||
const match = user.shared.multiplayerManager.GetMatchById(matchId);
|
||||
if (!(match instanceof Match)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
osuPacketWriter.MatchUpdate(match.generateMatchJSON());
|
||||
|
||||
for (const slot of match.slots) {
|
||||
if (slot.player === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const presenceBuffer = UserPresence(user, slot.player.id);
|
||||
const statusBuffer = StatusUpdate(user, slot.player.id);
|
||||
|
||||
if (presenceBuffer instanceof Buffer && statusBuffer instanceof Buffer) {
|
||||
user.addActionToQueue(presenceBuffer);
|
||||
user.addActionToQueue(statusBuffer);
|
||||
}
|
||||
|
||||
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
import { SharedContent } from "../interfaces/SharedContent";
|
||||
import { osu } from "../../osuTyping";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { User } from "../objects/User";
|
||||
const osu = require("osu-packet");
|
||||
|
||||
export function UserPresence(arg0:User | SharedContent, id:number) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
let sharedContent:SharedContent;
|
||||
export function UserPresence(arg0:User | Shared, id:number) {
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
let shared:Shared;
|
||||
if (arg0 instanceof User) {
|
||||
sharedContent = arg0.sharedContent;
|
||||
shared = arg0.shared;
|
||||
} else {
|
||||
sharedContent = arg0;
|
||||
shared = arg0;
|
||||
}
|
||||
|
||||
const userData = sharedContent.users.getById(id);
|
||||
const userData = shared.users.getById(id);
|
||||
|
||||
if (userData == null) return;
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { SharedContent } from "../interfaces/SharedContent";
|
||||
import { osu } from "../../osuTyping";
|
||||
import { Shared } from "../objects/Shared";
|
||||
import { User } from "../objects/User";
|
||||
const osu = require("osu-packet");
|
||||
|
||||
export function UserPresenceBundle(arg0:User | SharedContent) : Buffer {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
let sharedContent:SharedContent;
|
||||
export function UserPresenceBundle(arg0:User | Shared) : Buffer {
|
||||
const osuPacketWriter = osu.Bancho.Writer();
|
||||
let shared:Shared;
|
||||
if (arg0 instanceof User) {
|
||||
sharedContent = arg0.sharedContent;
|
||||
shared = arg0.shared;
|
||||
} else {
|
||||
sharedContent = arg0;
|
||||
shared = arg0;
|
||||
}
|
||||
|
||||
let userIds:Array<number> = new Array<number>();
|
||||
|
||||
for (let userData of sharedContent.users.getIterableItems()) {
|
||||
for (let userData of shared.users.getIterableItems()) {
|
||||
userIds.push(userData.id);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
"resolveJsonModule": true,
|
||||
"rootDir": "./",
|
||||
"outDir": "./build",
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"ES2021.String"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue