Make chat work

This commit is contained in:
Holly Stubbs 2022-11-19 15:06:03 +00:00
parent a09543b2fb
commit 3da964f5d6
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
8 changed files with 195 additions and 58 deletions

View file

@ -1,4 +1,5 @@
import { ConsoleHelper } from "../ConsoleHelper"; import { ConsoleHelper } from "../ConsoleHelper";
import { Channel } from "./objects/Channel";
import { ChatManager } from "./ChatManager"; import { ChatManager } from "./ChatManager";
import { Database } from "./objects/Database"; import { Database } from "./objects/Database";
import { LatLng } from "./objects/LatLng"; import { LatLng } from "./objects/LatLng";
@ -16,8 +17,6 @@ const config:any = JSON.parse(readFileSync("./config.json").toString());
// TODO: Port osu-packet to TypeScript // TODO: Port osu-packet to TypeScript
const osu = require("osu-packet"); const osu = require("osu-packet");
/*Streams = require("./Streams.js");*/
const DB:Database = new Database(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name, async () => { const DB: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 // 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 mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
@ -32,7 +31,7 @@ const streams:DataStreamArray = new DataStreamArray();
// ChatManager // ChatManager
const chatManager:ChatManager = new ChatManager(streams); const chatManager:ChatManager = new ChatManager(streams);
chatManager.AddChatChannel("osu", "The main channel"); chatManager.AddChatChannel("osu", "The main channel", true);
chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff"); chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
chatManager.AddChatChannel("english", "Talk in exclusively English"); chatManager.AddChatChannel("english", "Talk in exclusively English");
chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese"); chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese");
@ -82,42 +81,25 @@ if (config.redis.enabled) {
})(); })();
} else ConsoleHelper.printWarn("Redis is disabled!"); } else ConsoleHelper.printWarn("Redis is disabled!");
// Import packets
import { ChangeAction } from "./packets/ChangeAction";
import { Logout } from "./packets/Logout";
import { UserPresence } from "./packets/UserPresence";
import { UserStatsRequest } from "./packets/UserStatsRequest";
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
// User timeout interval // User timeout interval
setInterval(() => { setInterval(() => {
for (let User of users.getIterableItems()) { for (let User of users.getIterableItems()) {
if (User.uuid == "bot") continue; // Ignore the bot if (User.uuid == "bot") continue; // Ignore the bot
// Logout this user, they're clearly gone. // Logout this user, they're clearly gone.
// if (Date.now() >= User.timeoutTime) if (Date.now() >= User.timeoutTime) {
// Logout(User); Logout(User);
}
} }
}, 10000); }, 10000);
// Include packets
/*const ChangeAction = require("./Packets/ChangeAction.js"),
SendPublicMessage = require("./Packets/SendPublicMessage.js"),
Logout = require("./Packets/Logout.js"),
Spectator = require("./Spectator.js"),
SendPrivateMessage = require("./Packets/SendPrivateMessage.js"),
MultiplayerManager = require("./MultiplayerManager.js"),
SetAwayMessage = require("./Packets/SetAwayMessage.js"),
ChannelJoin = require("./Packets/ChannelJoin.js"),
ChannelPart = require("./Packets/ChannelPart.js"),
AddFriend = require("./Packets/AddFriend.js"),
RemoveFriend = require("./Packets/RemoveFriend.js"),
UserPresenceBundle = require("./Packets/UserPresenceBundle.js"),
UserPresence = require("./Packets/UserPresence.js"),
UserStatsRequest = require("./Packets/UserStatsRequest.js"),
MultiplayerInvite = require("./Packets/MultiplayerInvite.js"),
TourneyMatchSpecialInfo = require("./Packets/TourneyMatchSpecialInfo.js"),
TourneyMatchJoinChannel = require("./Packets/TourneyMatchSpecialInfo.js"),
TourneyMatchLeaveChannel = require("./Packets/TourneyLeaveMatchChannel.js");*/
import { ChangeAction } from "./packets/ChangeAction";
import { Logout } from "./packets/Logout";
import { UserPresence } from "./packets/UserPresence";
import { UserStatsRequest } from "./packets/UserStatsRequest";
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
const EMPTY_BUFFER = Buffer.alloc(0); const EMPTY_BUFFER = Buffer.alloc(0);
export async function HandleRequest(req:Request, res:Response, packet:Buffer) { export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
@ -163,7 +145,10 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
break; break;
case Packets.Client_SendPublicMessage: case Packets.Client_SendPublicMessage:
//SendPublicMessage(PacketUser, CurrentPacket.data); let channel = chatManager.GetChannelByName(CurrentPacket.data.target);
if (channel instanceof Channel) {
channel.SendMessage(PacketUser, CurrentPacket.data.message);
}
break; break;
case Packets.Client_Logout: case Packets.Client_Logout:

View file

@ -2,9 +2,12 @@ import { Channel } from "./objects/Channel";
import { ConsoleHelper } from "../ConsoleHelper"; import { ConsoleHelper } from "../ConsoleHelper";
import { FunkyArray } from "./objects/FunkyArray"; import { FunkyArray } from "./objects/FunkyArray";
import { DataStreamArray } from "./objects/DataStreamArray"; import { DataStreamArray } from "./objects/DataStreamArray";
import { User } from "./objects/User";
const osu = require("osu-packet");
export class ChatManager { export class ChatManager {
public chatChannels:FunkyArray<Channel> = new FunkyArray<Channel>(); public chatChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
public forceJoinChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
public streams:DataStreamArray; public streams:DataStreamArray;
public constructor(streams:DataStreamArray) { public constructor(streams:DataStreamArray) {
@ -13,7 +16,48 @@ export class ChatManager {
public AddChatChannel(name:string, description:string, forceJoin:boolean = false) { public AddChatChannel(name:string, description:string, forceJoin:boolean = false) {
const stream = this.streams.CreateStream(`chat_channel:${name}`, false); const stream = this.streams.CreateStream(`chat_channel:${name}`, false);
this.chatChannels.add(name, new Channel(name, description, stream, forceJoin)); const channel = new Channel(`#${name}`, description, stream);
this.chatChannels.add(channel.name, channel);
if (forceJoin) {
this.forceJoinChannels.add(name, channel);
}
ConsoleHelper.printChat(`Created chat channel [${name}]`); ConsoleHelper.printChat(`Created chat channel [${name}]`);
} }
public RemoveChatChannel(channel:Channel | string) {
if (channel instanceof Channel) {
channel.stream.Delete();
this.chatChannels.remove(channel.stream.name);
this.forceJoinChannels.remove(channel.stream.name)
} else {
const chatChannel = this.GetChannelByName(channel);
if (chatChannel instanceof Channel) {
chatChannel.stream.Delete();
this.chatChannels.remove(chatChannel.stream.name);
this.forceJoinChannels.remove(chatChannel.stream.name)
}
}
}
public GetChannelByName(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;
for (let channel of this.chatChannels.getIterableItems()) {
osuPacketWriter.ChannelAvailable({
channelName: channel.name,
channelTopic: channel.description,
channelUserCount: channel.userCount
});
}
user.addActionToQueue(osuPacketWriter.toBuffer);
}
} }

View file

@ -192,23 +192,13 @@ export async function LoginProcess(req:Request, res:Response, packet:Buffer, dat
// peppy pls, why // peppy pls, why
osuPacketWriter.ChannelListingComplete(); osuPacketWriter.ChannelListingComplete();
// Add user to #osu // Setup chat
osuPacketWriter.ChannelJoinSuccess("#osu"); chatManager.ForceJoinChannels(newUser);
//if (!Streams.isUserInStream("#osu", newUser.uuid)) chatManager.SendChannelListing(newUser);
// Streams.addUserToStream("#osu", newUser.uuid);
// List all channels out to the client
/*for (let i = 0; i < global.channels.length; i++) {
osuPacketWriter.ChannelAvailable({
channelName: global.channels[i].channelName,
channelTopic: global.channels[i].channelTopic,
channelUserCount: global.channels[i].channelUserCount
});
}*/
// Construct user's friends list // Construct user's friends list
const userFriends = await database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]); const userFriends = await database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]);
let friendsArray = new Array<number>; const friendsArray:Array<number> = new Array<number>();
for (let i = 0; i < userFriends.length; i++) { for (let i = 0; i < userFriends.length; i++) {
friendsArray.push(userFriends[i].friendsWith); friendsArray.push(userFriends[i].friendsWith);
} }

View file

@ -24,3 +24,17 @@ export function generateSession() : Promise<string> {
export function generateSessionSync() : string { export function generateSessionSync() : string {
return randomBytes(12).toString("hex"); return randomBytes(12).toString("hex");
} }
export function hexlify(data:Buffer) : string {
let out:string = "";
for (let i = 0; i < data.length; i++) {
const hex = data[i].toString(16);
if (hex.length === 1) {
out += `0${hex.toUpperCase()},`;
} else {
out += `${hex.toUpperCase()},`;
}
}
return out.slice(0, out.length - 1);
}

View file

@ -1,20 +1,70 @@
import { DataStream } from "./DataStream"; import { DataStream } from "./DataStream";
import { User } from "./User";
const osu = require("osu-packet");
export class Channel { export class Channel {
public name:string; public name:string;
public description:string; public description:string;
public userCount:number = 0; public stream:DataStream;
private stream:DataStream; private isLocked:boolean = false;
private forceJoin:boolean;
public constructor(name:string, description:string, stream:DataStream, forceJoin:boolean = false) { public constructor(name:string, description:string, stream:DataStream) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.stream = stream; this.stream = stream;
this.forceJoin = forceJoin;
} }
public SendMessage(message:string) { public get userCount() {
return this.stream.userCount;
}
public SendMessage(sender:User, message:string) {
const isBotCommand = message[0] === "!";
if (this.isLocked && !isBotCommand) {
return this.SendSystemMessage("This channel is currently locked", sender);
}
if (isBotCommand) {
if (message.split(" ")[0] === "!lock") {
this.isLocked = true;
}
}
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 SendSystemMessage(message:string, sendTo?:User) {
const osuPacketWriter = new osu.Bancho.Writer;
osuPacketWriter.SendMessage({
sendingClient: "System",
message: message,
target: this.name,
senderId: 1
});
if (sendTo instanceof User) {
sendTo.addActionToQueue(osuPacketWriter.toBuffer);
} else {
this.stream.Send(osuPacketWriter.toBuffer);
}
}
public Join(user:User) {
this.stream.AddUser(user);
const osuPacketWriter = new osu.Bancho.Writer;
osuPacketWriter.ChannelJoinSuccess(this.name);
user.addActionToQueue(osuPacketWriter.toBuffer);
}
public Leave(user:User) {
this.stream.RemoveUser(user);
} }
} }

View file

@ -3,12 +3,14 @@ import { Constants } from "../../Constants";
import { DataStreamArray } from "./DataStreamArray"; import { DataStreamArray } from "./DataStreamArray";
import { User } from "./User"; import { User } from "./User";
import { UserArray } from "./UserArray"; import { UserArray } from "./UserArray";
import { hexlify } from "../Util";
export class DataStream { export class DataStream {
private users:UserArray = new UserArray(); private users:UserArray = new UserArray();
private readonly name:string; public readonly name:string;
private readonly parent:DataStreamArray; private readonly parent:DataStreamArray;
private readonly removeWhenEmpty:boolean; private readonly removeWhenEmpty:boolean;
private inactive:boolean = false;
public constructor(name:string, parent:DataStreamArray, removeWhenEmpty:boolean) { public constructor(name:string, parent:DataStreamArray, removeWhenEmpty:boolean) {
this.name = name; this.name = name;
@ -16,30 +18,66 @@ export class DataStream {
this.removeWhenEmpty = removeWhenEmpty; this.removeWhenEmpty = removeWhenEmpty;
} }
private checkInactive() {
if (this.inactive) {
throw `Stream ${this.name} is inactive (deleted) and cannot be used here.`;
}
}
public get userCount() : number {
return this.users.getLength();
}
public AddUser(user:User) : void { public AddUser(user:User) : void {
this.checkInactive();
if (!(user.uuid in this.users.getItems())) { if (!(user.uuid in this.users.getItems())) {
this.users.add(user.uuid, user); this.users.add(user.uuid, user);
ConsoleHelper.printStream(`Added user [${user.username}|${user.uuid}] to stream [${this.name}]`); ConsoleHelper.printStream(`Added [${user.username}] to stream [${this.name}]`);
} }
} }
public RemoveUser(user:User) : void { public RemoveUser(user:User) : void {
this.checkInactive();
if (user.uuid in this.users.getItems()) { if (user.uuid in this.users.getItems()) {
this.users.remove(user.uuid); this.users.remove(user.uuid);
ConsoleHelper.printStream(`Removed user [${user.username}|${user.uuid}] from stream [${this.name}]`); ConsoleHelper.printStream(`Removed [${user.username}] from stream [${this.name}]`);
} }
if (this.removeWhenEmpty && this.users.getLength() === 0) { if (this.removeWhenEmpty && this.users.getLength() === 0) {
this.parent.remove(this.name); this.Delete();
} }
} }
public Delete() {
this.parent.DeleteStream(this);
}
public Deactivate() {
this.inactive = true;
}
public Send(data:Buffer) { public Send(data:Buffer) {
this.checkInactive();
for (let user of this.users.getIterableItems()) { for (let user of this.users.getIterableItems()) {
user.addActionToQueue(data); user.addActionToQueue(data);
} }
if (Constants.DEBUG) { if (Constants.DEBUG) {
ConsoleHelper.printStream(`Sent [${data.toString()}] to all users in stream [${this.name}]`); ConsoleHelper.printStream(`Sent [${data.toString()}] to all users in stream [${this.name}]`);
} }
} }
public SendWithExclusion(data:Buffer, exclude:User) {
this.checkInactive();
for (let user of this.users.getIterableItems()) {
if (user.uuid !== exclude.uuid) {
user.addActionToQueue(data);
}
}
if (Constants.DEBUG) {
ConsoleHelper.printStream(`Sent Buffer<${hexlify(data)}> to all users in stream [${this.name}] excluding user [${exclude.username}]`);
}
}
} }

View file

@ -10,6 +10,21 @@ export class DataStreamArray extends FunkyArray<DataStream> {
return dataStream; return dataStream;
} }
public DeleteStream(stream:DataStream | string) {
const isObject = stream instanceof DataStream;
if (isObject) {
stream.Deactivate();
this.remove(stream.name);
} else {
const dso = this.getByKey(stream);
if (dso != null) {
dso.Deactivate();
}
this.remove(stream);
}
ConsoleHelper.printStream(`Deleted stream [${isObject ? stream.name : stream}]`);
}
public RemoveUserFromAllStreams(user:User) { public RemoveUserFromAllStreams(user:User) {
for (let stream of this.getIterableItems()) { for (let stream of this.getIterableItems()) {
stream.RemoveUser(user); stream.RemoveUser(user);

View file

@ -6,6 +6,7 @@ import { DataStream } from "./DataStream";
import { UserArray } from "./UserArray"; import { UserArray } from "./UserArray";
import { DataStreamArray } from "./DataStreamArray"; import { DataStreamArray } from "./DataStreamArray";
import { ChatManager } from "../ChatManager"; import { ChatManager } from "../ChatManager";
import { StatusUpdate } from "../packets/StatusUpdate";
//const StatusUpdate = require("./Packets/StatusUpdate.js"); //const StatusUpdate = require("./Packets/StatusUpdate.js");
const rankingModes = [ const rankingModes = [
@ -146,7 +147,7 @@ export class User {
else this.pp = 0; else this.pp = 0;
if (userScoreUpdate || forceUpdate) { if (userScoreUpdate || forceUpdate) {
//StatusUpdate(this, this.id); StatusUpdate(this, this.id);
} }
} }
} }