diff --git a/server/chat-commands/admin.ts b/server/chat-commands/admin.ts
index ad3ede2b9d..92264ca9e0 100644
--- a/server/chat-commands/admin.ts
+++ b/server/chat-commands/admin.ts
@@ -71,7 +71,7 @@ export const commands: ChatCommands = {
html += Utils.html`
[${user.name}]
`;
}
- this.room.sendRankedUsers(`|html|${html}
`, rank as GroupSymbol);
+ room.sendRankedUsers(`|html|${html}
`, rank as GroupSymbol);
},
addrankhtmlboxhelp: [
`/addrankhtmlbox [rank], [message] - Shows everyone with the specified rank or higher a message, parsing HTML code contained. Requires: * # &`,
@@ -121,7 +121,7 @@ export const commands: ChatCommands = {
}
html = `|uhtml${(cmd === 'changerankuhtml' ? 'change' : '')}|${name}|${html}`;
- this.room.sendRankedUsers(html, rank as GroupSymbol);
+ room.sendRankedUsers(html, rank as GroupSymbol);
},
addrankuhtmlhelp: [
`/addrankuhtml [rank], [name], [message] - Shows everyone with the specified rank or higher a message that can change, parsing HTML code contained. Requires: * # &`,
@@ -324,7 +324,7 @@ export const commands: ChatCommands = {
// respawn simulator processes
void Rooms.PM.respawn();
// broadcast the new formats list to clients
- Rooms.global.send(Rooms.global.formatListText);
+ Rooms.global.sendAll(Rooms.global.formatListText);
this.sendReply("Formats have been hot-patched.");
} else if (target === 'loginserver') {
@@ -787,7 +787,7 @@ export const commands: ChatCommands = {
refreshpage(target, room, user) {
if (!this.can('lockdown')) return false;
- Rooms.global.send('|refresh|');
+ Rooms.global.sendAll('|refresh|');
const logRoom = Rooms.get('staff') || room;
logRoom.roomlog(`${user.name} used /refreshpage`);
},
diff --git a/server/chat-commands/core.ts b/server/chat-commands/core.ts
index 43effde143..5ed6e790cc 100644
--- a/server/chat-commands/core.ts
+++ b/server/chat-commands/core.ts
@@ -507,8 +507,8 @@ export const commands: ChatCommands = {
target = this.splitTarget(target);
const targetUser = this.targetUser;
if (this.targetUsername === '~') {
- this.room = Rooms.global;
this.pmTarget = null;
+ this.room = null;
} else if (!targetUser) {
let error = `User ${this.targetUsername} not found. Did you misspell their name?`;
error = `|pm|${this.user.getIdentity()}| ${this.targetUsername}|/error ${error}`;
@@ -516,7 +516,7 @@ export const commands: ChatCommands = {
return;
} else {
this.pmTarget = targetUser;
- this.room = null!;
+ this.room = null;
}
if (targetUser && !targetUser.connected) {
@@ -547,7 +547,7 @@ export const commands: ChatCommands = {
const targetUser = this.pmTarget!; // not room means it's a PM
- if (!targetRoom || targetRoom === Rooms.global) {
+ if (!targetRoom) {
return this.errorReply(`The room "${target}" was not found.`);
}
if (!targetRoom.checkModjoin(targetUser)) {
@@ -1393,7 +1393,7 @@ export const commands: ChatCommands = {
}
const targetRoom = Rooms.get(target);
- if (!targetRoom || targetRoom === Rooms.global || (
+ if (!targetRoom || (
targetRoom.settings.isPrivate && !user.inRooms.has(targetRoom.roomid) && !user.games.has(targetRoom.roomid)
)) {
const roominfo = {id: target, error: 'not found or access denied'};
diff --git a/server/chat-commands/info.ts b/server/chat-commands/info.ts
index ddffdbb6fb..3236854ac9 100644
--- a/server/chat-commands/info.ts
+++ b/server/chat-commands/info.ts
@@ -19,10 +19,8 @@ export const commands: ChatCommands = {
alt: 'whois',
alts: 'whois',
whoare: 'whois',
- whois(target, room, user, connection, cmd) {
- let usedRoom: ChatRoom | GameRoom | GlobalRoom = room;
- if (usedRoom?.roomid === 'staff' && !this.runBroadcast()) return;
- if (!usedRoom) usedRoom = Rooms.global;
+ whois(target, room: Room | null, user, connection, cmd) {
+ if (room?.roomid === 'staff' && !this.runBroadcast()) return;
const targetUser = this.targetUserOrSelf(target, user.group === ' ');
const showAll = (cmd === 'ip' || cmd === 'whoare' || cmd === 'alt' || cmd === 'alts');
if (!targetUser) {
@@ -43,8 +41,8 @@ export const commands: ChatCommands = {
buf += ` (trusted${targetUser.id === trusted ? `` : `: ${trusted}`})`;
}
if (!targetUser.connected) buf += ` (offline)`;
- const roomauth = usedRoom.auth.getDirect(targetUser.id);
- if (Config.groups[roomauth]?.name) {
+ const roomauth = room?.auth.getDirect(targetUser.id);
+ if (roomauth && Config.groups[roomauth]?.name) {
buf += Utils.html`
${Config.groups[roomauth].name} (${roomauth})`;
}
if (Config.groups[targetUser.group]?.name) {
@@ -85,7 +83,7 @@ export const commands: ChatCommands = {
}
const canViewAlts = (user === targetUser || user.can('alts', targetUser));
const canViewPunishments = canViewAlts ||
- (usedRoom.settings.isPrivate !== true && user.can('mute', targetUser, usedRoom) && targetUser.id in usedRoom.users);
+ (room && room.settings.isPrivate !== true && user.can('mute', targetUser, room) && targetUser.id in room.users);
const canViewSecretRooms = user === targetUser || (canViewAlts && targetUser.locked) || user.can('makeroom');
buf += `
`;
@@ -2631,13 +2629,15 @@ export const commands: ChatCommands = {
export const pages: PageTable = {
punishments(query, user) {
this.title = 'Punishments';
+ const room = this.extractRoom();
+ if (!room) return;
+
let buf = "";
- this.extractRoom();
if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
- if (!this.room.persist) return;
- if (!this.can('mute', null, this.room)) return;
+ if (!room.persist) return;
+ if (!this.can('mute', null, room)) return;
// Ascending order
- const sortedPunishments = Array.from(Punishments.getPunishments(this.room.roomid))
+ const sortedPunishments = Array.from(Punishments.getPunishments(room.roomid))
.sort((a, b) => a[1].expireTime - b[1].expireTime);
const sP = new Map();
for (const punishment of sortedPunishments) {
diff --git a/server/chat-commands/moderation.ts b/server/chat-commands/moderation.ts
index 5470dc23d7..1e7e86a7ca 100644
--- a/server/chat-commands/moderation.ts
+++ b/server/chat-commands/moderation.ts
@@ -227,7 +227,7 @@ export const commands: ChatCommands = {
let userLookup = '';
if (cmd === 'roomauth1') userLookup = `\n\nTo look up auth for a user, use /userauth ${target}`;
let targetRoom = room;
- if (target) targetRoom = Rooms.search(target) as ChatRoom | GameRoom;
+ if (target) targetRoom = Rooms.search(target)!;
if (!targetRoom || targetRoom.roomid === 'global' || !targetRoom.checkModjoin(user)) {
return this.errorReply(`The room "${target}" does not exist.`);
}
@@ -262,7 +262,7 @@ export const commands: ChatCommands = {
const also = buffer.length === 0 ? `` : ` also`;
buffer.push(`${curRoom.title} is a ${roomType}subroom of ${curRoom.parent.title}, so ${curRoom.parent.title} users${inheritedUserType}${also} have authority in this room.`);
}
- curRoom = curRoom.parent as ChatRoom | GameRoom;
+ curRoom = curRoom.parent;
}
if (!buffer.length) {
connection.popup(`The room '${targetRoom.title}' has no auth. ${userLookup}`);
@@ -375,7 +375,7 @@ export const commands: ChatCommands = {
leave: 'part',
part(target, room, user, connection) {
const targetRoom = target ? Rooms.search(target) : room;
- if (!targetRoom || targetRoom === Rooms.global) {
+ if (!targetRoom) {
if (target.startsWith('view-')) return;
return this.errorReply(`The room '${target}' does not exist.`);
}
@@ -1829,7 +1829,7 @@ export const commands: ChatCommands = {
blacklists: 'showblacklist',
showbl: 'showblacklist',
showblacklist(target, room, user, connection, cmd) {
- if (target) room = Rooms.search(target) as ChatRoom | GameRoom;
+ if (target) room = Rooms.search(target)!;
if (!room) return this.errorReply(`The room "${target}" was not found.`);
if (!this.can('mute', null, room)) return false;
const SOON_EXPIRING_TIME = 3 * 30 * 24 * 60 * 60 * 1000; // 3 months
diff --git a/server/chat-plugins/announcements.ts b/server/chat-plugins/announcements.ts
index e7e4ac639d..35197b4662 100644
--- a/server/chat-plugins/announcements.ts
+++ b/server/chat-plugins/announcements.ts
@@ -7,11 +7,11 @@ import {Utils} from '../../lib/utils';
export class Announcement {
readonly activityId: 'announcement';
announcementNumber: number;
- room: ChatRoom | GameRoom;
+ room: Room;
source: string;
timeout: NodeJS.Timer | null;
timeoutMins: number;
- constructor(room: ChatRoom | GameRoom, source: string) {
+ constructor(room: Room, source: string) {
this.activityId = 'announcement';
this.announcementNumber = room.nextGameNumber();
this.room = room;
diff --git a/server/chat-plugins/blackjack.ts b/server/chat-plugins/blackjack.ts
index bc5d2cde3b..589faa26fc 100644
--- a/server/chat-plugins/blackjack.ts
+++ b/server/chat-plugins/blackjack.ts
@@ -18,7 +18,7 @@ type Symbols = '♥' | '♦' | '♣' | '♠';
type SymbolName = 'Hearts' | 'Diamonds' | 'Clubs' | 'Spades';
export class Blackjack extends Rooms.RoomGame {
- room: ChatRoom | GameRoom;
+ room: Room;
blackjack: boolean;
@@ -56,7 +56,7 @@ export class Blackjack extends Rooms.RoomGame {
playerTable: {[k: string]: BlackjackPlayer};
gameNumber: number;
- constructor(room: ChatRoom | GameRoom, user: User, autostartMinutes = 0) {
+ constructor(room: Room, user: User, autostartMinutes = 0) {
super(room);
this.gameNumber = room.nextGameNumber();
this.room = room;
diff --git a/server/chat-plugins/chat-monitor.ts b/server/chat-plugins/chat-monitor.ts
index 1744ed85ae..e353ca3305 100644
--- a/server/chat-plugins/chat-monitor.ts
+++ b/server/chat-plugins/chat-monitor.ts
@@ -6,7 +6,7 @@ type FilterWord = [RegExp, string, string, string | null, number];
type MonitorHandler = (
this: CommandContext,
line: FilterWord,
- room: ChatRoom | GameRoom | null,
+ room: Room | null,
user: User,
message: string,
lcMessage: string,
diff --git a/server/chat-plugins/daily-spotlight.ts b/server/chat-plugins/daily-spotlight.ts
index 2b56a8e1cb..813e3b47c5 100644
--- a/server/chat-plugins/daily-spotlight.ts
+++ b/server/chat-plugins/daily-spotlight.ts
@@ -52,18 +52,20 @@ export const destroy = () => {
export const pages: PageTable = {
async spotlights(query, user, connection) {
this.title = 'Daily Spotlights';
- this.extractRoom();
+ const room = this.extractRoom();
+ if (!room) return;
+
let buf = `Daily Spotlights
`;
- if (!spotlights[this.room.roomid]) {
+ if (!spotlights[room.roomid]) {
buf += `
This room has no daily spotlights.
`;
} else {
- for (const key in spotlights[this.room.roomid]) {
+ for (const key in spotlights[room.roomid]) {
buf += `${key}: | `;
- for (const [i, spotlight] of spotlights[this.room.roomid][key].entries()) {
+ for (const [i, spotlight] of spotlights[room.roomid][key].entries()) {
const html = await renderSpotlight(spotlight.description, spotlight.image);
buf += `| ${i ? i : 'Current'} | ${html} |
`;
// @ts-ignore room is definitely a proper room here.
- if (!user.can('announce', null, this.room)) break;
+ if (!user.can('announce', null, room)) break;
}
buf += '
';
}
diff --git a/server/chat-plugins/hangman.ts b/server/chat-plugins/hangman.ts
index 614e6d0026..9c120871b1 100644
--- a/server/chat-plugins/hangman.ts
+++ b/server/chat-plugins/hangman.ts
@@ -18,7 +18,7 @@ export class Hangman extends Rooms.RoomGame {
lastGuesser: string;
wordSoFar: string[];
- constructor(room: ChatRoom | GameRoom, user: User, word: string, hint = '') {
+ constructor(room: Room, user: User, word: string, hint = '') {
super(room);
this.gameNumber = room.nextGameNumber();
diff --git a/server/chat-plugins/jeopardy.ts b/server/chat-plugins/jeopardy.ts
index 48b64ddab2..8bfae7e72a 100644
--- a/server/chat-plugins/jeopardy.ts
+++ b/server/chat-plugins/jeopardy.ts
@@ -40,7 +40,7 @@ export class Jeopardy extends Rooms.RoomGame {
finals: boolean;
gameNumber: number;
- constructor(room: ChatRoom | GameRoom, user: User, categoryCount: number, questionCount: number) {
+ constructor(room: Room, user: User, categoryCount: number, questionCount: number) {
super(room);
this.gameNumber = room.nextGameNumber();
this.playerTable = Object.create(null);
diff --git a/server/chat-plugins/lottery.ts b/server/chat-plugins/lottery.ts
index 9981724989..ce98b2c256 100644
--- a/server/chat-plugins/lottery.ts
+++ b/server/chat-plugins/lottery.ts
@@ -264,19 +264,21 @@ export const commands: ChatCommands = {
export const pages: PageTable = {
lottery(query, user) {
- this.extractRoom();
this.title = 'Lottery';
+ const room = this.extractRoom();
+ if (!room) return;
+
let buf = '';
- const lottery = lotteries[this.room.roomid];
+ const lottery = lotteries[room.roomid];
if (!lottery) {
- buf += `
There is no lottery running in ${this.room.title}
`;
+ buf += `There is no lottery running in ${room.title}
`;
return buf;
}
buf += `${lottery.name}
${lottery.markup}
`;
if (lottery.running) {
const userSignedUp = lottery.participants[user.latestIp] ||
Object.values(lottery.participants).map(toID).includes(user.id);
- buf += ``;
+ buf += ``;
} else {
buf += 'This lottery has already ended. The winners are:
';
buf += '';
diff --git a/server/chat-plugins/modlog.ts b/server/chat-plugins/modlog.ts
index 97a9a2ba1b..32fbd18bae 100644
--- a/server/chat-plugins/modlog.ts
+++ b/server/chat-plugins/modlog.ts
@@ -569,7 +569,7 @@ export const commands: ChatCommands = {
pl: 'modlog',
timedmodlog: 'modlog',
modlog(target, room, user, connection, cmd) {
- if (!room) room = Rooms.get('global') as ChatRoom | GameRoom;
+ if (!room) (room as Room | undefined) = Rooms.get('global');
let roomid: RoomID = (room.roomid === 'staff' ? 'global' : room.roomid);
if (target.includes(',')) {
diff --git a/server/chat-plugins/poll.ts b/server/chat-plugins/poll.ts
index dc9502f226..1169f4df61 100644
--- a/server/chat-plugins/poll.ts
+++ b/server/chat-plugins/poll.ts
@@ -14,7 +14,7 @@ interface Option {
export class Poll {
readonly activityId: 'poll';
pollNumber: number;
- room: ChatRoom | GameRoom;
+ room: Room;
question: string;
supportHTML: boolean;
multiPoll: boolean;
@@ -26,7 +26,7 @@ export class Poll {
timeoutMins: number;
isQuiz: boolean;
options: Map;
- constructor(room: ChatRoom | GameRoom, questionData: QuestionData, options: string[], multi: boolean) {
+ constructor(room: Room, questionData: QuestionData, options: string[], multi: boolean) {
this.activityId = 'poll';
this.pollNumber = room.nextGameNumber();
this.room = room;
diff --git a/server/chat-plugins/scavenger-games.ts b/server/chat-plugins/scavenger-games.ts
index 85e7025b42..29d312d714 100644
--- a/server/chat-plugins/scavenger-games.ts
+++ b/server/chat-plugins/scavenger-games.ts
@@ -663,12 +663,12 @@ const MODES: {[k: string]: GameMode | string} = {
};
export class ScavengerGameTemplate {
- room: ChatRoom | GameRoom;
+ room: Room;
playerlist: null | string[];
timer: NodeJS.Timer | null;
[k: string]: any;
- constructor(room: GameRoom | ChatRoom) {
+ constructor(room: Room) {
this.room = room;
this.playerlist = null;
this.timer = null;
@@ -694,7 +694,7 @@ export class ScavengerGameTemplate {
}
}
-const LoadGame = function (room: ChatRoom | GameRoom, gameid: string) {
+const LoadGame = function (room: Room, gameid: string) {
let game = MODES[gameid];
if (!game) return false; // invalid id
if (typeof game === 'string') game = MODES[game];
diff --git a/server/chat-plugins/scavengers.ts b/server/chat-plugins/scavengers.ts
index 7698a1608e..42607328f9 100644
--- a/server/chat-plugins/scavengers.ts
+++ b/server/chat-plugins/scavengers.ts
@@ -202,7 +202,7 @@ if (LeaderboardRoom) {
LeaderboardRoom.scavLeaderboard.scavsPlayerLeaderboard = PlayerLeaderboard;
}
-function formatQueue(queue: QueuedHunt[] | undefined, viewer: User, room: ChatRoom | GameRoom, broadcasting?: boolean) {
+function formatQueue(queue: QueuedHunt[] | undefined, viewer: User, room: Room, broadcasting?: boolean) {
const showStaff = viewer.can('mute', null, room) && !broadcasting;
const queueDisabled = room.settings.scavSettings?.scavQueueDisabled;
const timerDuration = room.settings.scavSettings?.defaultScavTimer || DEFAULT_TIMER_DURATION;
@@ -353,7 +353,7 @@ export class ScavengerHunt extends Rooms.RoomGame {
[k: string]: any; // for purposes of adding new temporary properties for the purpose of twists.
constructor(
- room: ChatRoom | GameRoom,
+ room: Room,
staffHost: User | FakeUser,
hosts: FakeUser[],
gameType: GameTypes,
@@ -952,7 +952,7 @@ export class ScavengerHunt extends Rooms.RoomGame {
return ips.filter((ip, index) => ips.indexOf(ip) === index).length;
}
- static parseHosts(hostArray: string[], room: ChatRoom | GameRoom, allowOffline?: boolean) {
+ static parseHosts(hostArray: string[], room: Room, allowOffline?: boolean) {
const hosts = [];
for (const u of hostArray) {
const id = toID(u);
@@ -2345,11 +2345,13 @@ const ScavengerCommands: ChatCommands = {
export const pages: PageTable = {
recycledHunts(query, user, connection) {
this.title = 'Recycled Hunts';
+ const room = this.extractRoom();
+ if (!room) return;
+
let buf = "";
- this.extractRoom();
if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
- if (!this.room.persist) return;
- if (!this.can('mute', null, this.room)) return;
+ if (!room.persist) return;
+ if (!this.can('mute', null, room)) return;
buf += `List of recycled Scavenger hunts
`;
buf += `
`;
for (const hunt of scavengersData.recycledHunts) {
diff --git a/server/chat-plugins/thing-of-the-day.ts b/server/chat-plugins/thing-of-the-day.ts
index 50afa65630..d01eca346f 100644
--- a/server/chat-plugins/thing-of-the-day.ts
+++ b/server/chat-plugins/thing-of-the-day.ts
@@ -680,7 +680,7 @@ export const otdCommands: ChatCommands = {
return handler.generateWinnerDisplay().then(text => {
if (!text) return this.errorReply("There is no winner yet.");
this.sendReplyBox(text);
- this.room.update();
+ room.update();
});
},
};
diff --git a/server/chat-plugins/trivia.ts b/server/chat-plugins/trivia.ts
index 35924bf3b7..7baeb9e473 100644
--- a/server/chat-plugins/trivia.ts
+++ b/server/chat-plugins/trivia.ts
@@ -320,7 +320,7 @@ class Trivia extends Rooms.RoomGame {
curAnswers: string[];
askedAt: number[];
constructor(
- room: GameRoom | ChatRoom, mode: string, category: string,
+ room: Room, mode: string, category: string,
length: string, questions: TriviaQuestion[], isRandomMode = false
) {
super(room);
diff --git a/server/chat-plugins/uno.ts b/server/chat-plugins/uno.ts
index 574c2dfe0a..2820a26507 100644
--- a/server/chat-plugins/uno.ts
+++ b/server/chat-plugins/uno.ts
@@ -100,7 +100,7 @@ export class UnoGame extends Rooms.RoomGame {
isPlusFour: boolean;
gameNumber: number;
- constructor(room: ChatRoom | GameRoom, cap: number, suppressMessages: boolean) {
+ constructor(room: Room, cap: number, suppressMessages: boolean) {
super(room);
this.playerTable = Object.create(null);
diff --git a/server/chat-plugins/wifi.ts b/server/chat-plugins/wifi.ts
index 2aef157d36..5f2037e44a 100644
--- a/server/chat-plugins/wifi.ts
+++ b/server/chat-plugins/wifi.ts
@@ -34,7 +34,7 @@ class Giveaway {
gaNumber: number;
host: User;
giver: User;
- room: ChatRoom | GameRoom;
+ room: Room;
ot: string;
tid: string;
prize: string;
@@ -45,7 +45,7 @@ class Giveaway {
sprite: string;
constructor(
- host: User, giver: User, room: ChatRoom | GameRoom,
+ host: User, giver: User, room: Room,
ot: string, tid: string, prize: string
) {
this.gaNumber = room.nextGameNumber();
@@ -111,15 +111,15 @@ class Giveaway {
return false;
}
- static checkBanned(room: ChatRoom | GameRoom, user: User) {
+ static checkBanned(room: Room, user: User) {
return Punishments.getRoomPunishType(room, toID(user)) === 'GIVEAWAYBAN';
}
- static ban(room: ChatRoom | GameRoom, user: User, reason: string) {
+ static ban(room: Room, user: User, reason: string) {
Punishments.roomPunish(room, user, ['GIVEAWAYBAN', toID(user), Date.now() + BAN_DURATION, reason]);
}
- static unban(room: ChatRoom | GameRoom, user: User) {
+ static unban(room: Room, user: User) {
Punishments.roomUnpunish(room, toID(user), 'GIVEAWAYBAN', false);
}
@@ -219,7 +219,7 @@ export class QuestionGiveaway extends Giveaway {
winner: User | null;
constructor(
- host: User, giver: User, room: ChatRoom | GameRoom, ot: string, tid: string,
+ host: User, giver: User, room: Room, ot: string, tid: string,
prize: string, question: string, answers: string[]
) {
super(host, giver, room, ot, tid, prize);
@@ -359,7 +359,7 @@ export class LotteryGiveaway extends Giveaway {
maxWinners: number;
constructor(
- host: User, giver: User, room: ChatRoom | GameRoom, ot: string,
+ host: User, giver: User, room: Room, ot: string,
tid: string, prize: string, winners: number
) {
super(host, giver, room, ot, tid, prize);
@@ -484,7 +484,7 @@ export class LotteryGiveaway extends Giveaway {
export class GTSGiveaway {
gtsNumber: number;
- room: ChatRoom | GameRoom;
+ room: Room;
giver: User;
left: number;
summary: string;
@@ -497,7 +497,7 @@ export class GTSGiveaway {
timer: NodeJS.Timer | null;
constructor(
- room: ChatRoom | GameRoom, giver: User, amount: number,
+ room: Room, giver: User, amount: number,
summary: string, deposit: string, lookfor: string
) {
this.gtsNumber = room.nextGameNumber();
diff --git a/server/chat.ts b/server/chat.ts
index acb0f46beb..7407ef7509 100644
--- a/server/chat.ts
+++ b/server/chat.ts
@@ -35,7 +35,7 @@ export interface PageTable {
export type ChatHandler = (
this: CommandContext,
target: string,
- room: ChatRoom | GameRoom,
+ room: Room,
user: User,
connection: Connection,
cmd: string,
@@ -66,7 +66,7 @@ export type ChatFilter = (
this: CommandContext,
message: string,
user: User,
- room: ChatRoom | GameRoom | null,
+ room: Room | null,
connection: Connection,
targetUser: User | null,
originalMessage: string
@@ -203,7 +203,7 @@ class MessageContext {
export class PageContext extends MessageContext {
readonly connection: Connection;
- room: Room;
+ room: Room | null;
pageid: string;
initialized: boolean;
title: string;
@@ -211,7 +211,7 @@ export class PageContext extends MessageContext {
super(options.user, options.language);
this.connection = options.connection;
- this.room = Rooms.get('global')!;
+ this.room = null;
this.pageid = options.pageid;
this.initialized = false;
@@ -237,16 +237,16 @@ export class PageContext extends MessageContext {
const room = Rooms.get(parts[2]);
if (!room) {
this.send(`Invalid room.
`);
- return false;
+ return null;
}
this.room = room;
- return room.roomid;
+ return room;
}
send(content: string) {
if (!content.startsWith('|deinit')) {
- const roomid = this.room !== Rooms.global ? `[${this.room.roomid}] ` : '';
+ const roomid = this.room ? `[${this.room.roomid}] ` : '';
if (!this.initialized) {
content = `|init|html\n|title|${roomid}${this.title}\n|pagehtml|${content}`;
this.initialized = true;
@@ -295,10 +295,18 @@ export class PageContext extends MessageContext {
}
}
+/**
+ * This is a message sent in a PM or to a chat/battle room.
+ *
+ * There are three cases to be aware of:
+ * - PM to user: `context.pmTarget` will exist and `context.room` will be `null`
+ * - message to room: `context.room` will exist and `context.pmTarget` will be `null`
+ * - console command (PM to `~`): `context.pmTarget` and `context.room` will both be `null`
+ */
export class CommandContext extends MessageContext {
message: string;
pmTarget: User | null;
- room: Room;
+ room: Room | null;
connection: Connection;
cmd: string;
cmdToken: string;
@@ -312,8 +320,8 @@ export class CommandContext extends MessageContext {
inputUsername: string;
constructor(
options:
- {message: string, room: Room, user: User, connection: Connection} &
- Partial<{pmTarget: User | null, cmd: string, cmdToken: string, target: string, fullCmd: string}>
+ {message: string, user: User, connection: Connection} &
+ Partial<{room: Room | null, pmTarget: User | null, cmd: string, cmdToken: string, target: string, fullCmd: string}>
) {
super(
options.user, options.room && options.room.settings.language ?
@@ -365,7 +373,7 @@ export class CommandContext extends MessageContext {
message = this.run(commandHandler);
} else {
if (commandHandler === '!') {
- if (this.room === Rooms.global) {
+ if (!this.room) {
return this.popupReply(`You tried use "${message}" as a global command, but it is not a global command.`);
} else if (this.room) {
return this.popupReply(`You tried to send "${message}" to the room "${this.room.roomid}" but it failed because you were not in that room.`);
@@ -394,16 +402,14 @@ export class CommandContext extends MessageContext {
// Output the message
if (message && typeof message.then === 'function') {
- message.then(() => this.update());
- } else if (message && message !== true) {
- if (this.pmTarget) {
- Chat.sendPM(message, this.user, this.pmTarget);
- } else {
- this.room.add(`|c|${this.user.getIdentity(this.room.roomid)}|${message}`);
- if (this.room && this.room.game && this.room.game.onLogMessage) {
- this.room.game.onLogMessage(message, this.user);
+ void (message as Promise).then(resolvedMessage => {
+ if (resolvedMessage && resolvedMessage !== true) {
+ this.sendChatMessage(resolvedMessage);
}
- }
+ this.update();
+ });
+ } else if (message && message !== true) {
+ this.sendChatMessage(message);
}
this.update();
@@ -411,6 +417,19 @@ export class CommandContext extends MessageContext {
return message;
}
+ sendChatMessage(message: string) {
+ if (this.pmTarget) {
+ Chat.sendPM(message, this.user, this.pmTarget);
+ } else if (this.room) {
+ this.room.add(`|c|${this.user.getIdentity(this.room.roomid)}|${message}`);
+ if (this.room.game && this.room.game.onLogMessage) {
+ this.room.game.onLogMessage(message, this.user);
+ }
+ } else {
+ this.connection.popup(`Your message could not be sent:\n\n${message}\n\nIt needs to be sent to a user or room.`);
+ }
+ }
+
splitCommand(message = this.message, recursing = false): '!' | undefined | ChatHandler {
this.cmd = '';
this.cmdToken = '';
@@ -510,11 +529,7 @@ export class CommandContext extends MessageContext {
this.target = target;
this.fullCmd = fullCmd;
- const requireGlobalCommand = (
- this.pmTarget ||
- this.room === Rooms.global ||
- (this.room && !(this.user.id in this.room.users))
- );
+ const requireGlobalCommand = !(this.room && this.user.id in this.room.users);
if (typeof commandHandler === 'function' && requireGlobalCommand) {
const baseCmd = typeof curCommands[cmd] === 'string' ? curCommands[cmd] : cmd;
@@ -604,7 +619,7 @@ export class CommandContext extends MessageContext {
return this.room.game.onChatMessage(this.message, this.user);
}
pmTransform(originalMessage: string) {
- if (!this.pmTarget && this.room !== Rooms.global) throw new Error(`Not a PM`);
+ if (this.room) throw new Error(`Not a PM`);
const targetIdentity = this.pmTarget ? this.pmTarget.getIdentity() : '~';
const prefix = `|pm|${this.user.getIdentity()}|${targetIdentity}|`;
return originalMessage.split('\n').map(message => {
@@ -630,7 +645,7 @@ export class CommandContext extends MessageContext {
this.add(data);
} else {
// not broadcasting
- if (this.pmTarget || this.room === Rooms.global) {
+ if (!this.room) {
data = this.pmTransform(data);
this.connection.send(data);
} else {
@@ -651,36 +666,33 @@ export class CommandContext extends MessageContext {
this.connection.popup(message);
}
add(data: string) {
- if (this.pmTarget) {
+ if (!this.room) {
data = this.pmTransform(data);
this.user.send(data);
- if (this.pmTarget !== this.user) this.pmTarget.send(data);
+ if (this.pmTarget !== this.user) this.pmTarget!.send(data);
return;
}
this.room.add(data);
}
send(data: string) {
- if (this.pmTarget) {
+ if (!this.room) {
data = this.pmTransform(data);
this.user.send(data);
- if (this.pmTarget !== this.user) this.pmTarget.send(data);
+ if (this.pmTarget !== this.user) this.pmTarget!.send(data);
return;
}
this.room.send(data);
}
sendModCommand(data: string) {
- this.room.sendModsByUser(this.user, data);
+ this.room!.sendModsByUser(this.user, data);
}
- privateModCommand() {
- throw new Error(`this.privateModCommand has been renamed to this.privateModAction, which no longer writes to modlog.`);
- }
privateModAction(msg: string) {
- this.room.sendMods(msg);
+ this.room!.sendMods(msg);
this.roomlog(msg);
}
globalModlog(action: string, user: string | User | null, note: string) {
- let buf = `(${this.room.roomid}) ${action}: `;
+ let buf = `(${this.room ? this.room.roomid : 'global'}) ${action}: `;
if (user) {
if (typeof user === 'string') {
buf += `[${user}]`;
@@ -696,7 +708,7 @@ export class CommandContext extends MessageContext {
buf += note.replace(/\n/gm, ' ');
Rooms.global.modlog(buf);
- if (this.room !== Rooms.global) this.room.modlog(buf);
+ if (this.room) this.room.modlog(buf);
}
modlog(
action: string,
@@ -704,7 +716,7 @@ export class CommandContext extends MessageContext {
note: string | null = null,
options: Partial<{noalts: any, noip: any}> = {}
) {
- let buf = `(${this.room.roomid}) ${action}: `;
+ let buf = `(${this.room ? this.room.roomid : 'global'}) ${action}: `;
if (user) {
if (typeof user === 'string') {
buf += `[${toID(user)}]`;
@@ -722,27 +734,20 @@ export class CommandContext extends MessageContext {
buf += ` by ${this.user.id}`;
if (note) buf += `: ${note.replace(/\n/gm, ' ')}`;
- this.room.modlog(buf);
+ (this.room || Rooms.global).modlog(buf);
}
roomlog(data: string) {
- if (this.pmTarget) return;
- this.room.roomlog(data);
- }
- logEntry() {
- throw new Error(`this.logEntry has been renamed to this.roomlog.`);
- }
- addModCommand() {
- throw new Error(`this.addModCommand has been renamed to this.addModAction, which no longer writes to modlog.`);
+ if (this.room) this.room.roomlog(data);
}
addModAction(msg: string) {
- this.room.addByUser(this.user, msg);
+ this.room!.addByUser(this.user, msg);
}
update() {
if (this.room) this.room.update();
}
filter(message: string, targetUser: User | null = null) {
- if (!this.room || this.room.roomid === 'global') return null;
- return Chat.filter(this, message, this.user, this.room as GameRoom | ChatRoom, this.connection, targetUser);
+ if (!this.room) return null;
+ return Chat.filter(this, message, this.user, this.room, this.connection, targetUser);
}
statusfilter(status: string) {
return Chat.statusfilter(status, this.user);
@@ -771,14 +776,14 @@ export class CommandContext extends MessageContext {
return true;
}
- if (this.room instanceof Rooms.GlobalRoom) {
- this.errorReply(`You have no one to broadcast this to.`);
+ if (this.room && !this.user.can('broadcast', null, this.room)) {
+ this.errorReply(`You need to be voiced to broadcast this command's information.`);
this.errorReply(`To see it for yourself, use: /${this.message.slice(1)}`);
return false;
}
- if (!this.pmTarget && !this.user.can('broadcast', null, this.room)) {
- this.errorReply(`You need to be voiced to broadcast this command's information.`);
+ if (!this.room && !this.pmTarget) {
+ this.errorReply(`Broadcasting a command with "!" in a PM or chatroom will show it that user or room.`);
this.errorReply(`To see it for yourself, use: /${this.message.slice(1)}`);
return false;
}
@@ -820,9 +825,9 @@ export class CommandContext extends MessageContext {
if (this.pmTarget) {
this.sendReply('|c~|' + (suppressMessage || this.message));
} else {
- this.sendReply('|c|' + this.user.getIdentity(this.room.roomid) + '|' + (suppressMessage || this.message));
+ this.sendReply('|c|' + this.user.getIdentity(this.room ? this.room.roomid : '') + '|' + (suppressMessage || this.message));
}
- if (!ignoreCooldown && !this.pmTarget) {
+ if (!ignoreCooldown && this.room) {
this.room.lastBroadcast = this.broadcastMessage;
this.room.lastBroadcastTime = Date.now();
}
@@ -831,19 +836,15 @@ export class CommandContext extends MessageContext {
}
/* The sucrase transformation of optional chaining is too expensive to be used in a hot function like this. */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
- canTalk(message: string, room?: GameRoom | ChatRoom | null, targetUser?: User | null): string | null;
- canTalk(message?: null, room?: GameRoom | ChatRoom | null, targetUser?: User | null): true | null;
- canTalk(message: string | null = null, room: GameRoom | ChatRoom | null = null, targetUser: User | null = null) {
+ canTalk(message: string, room?: Room | null, targetUser?: User | null): string | null;
+ canTalk(message?: null, room?: Room | null, targetUser?: User | null): true | null;
+ canTalk(message: string | null = null, room: Room | null = null, targetUser: User | null = null) {
if (!targetUser && this.pmTarget) {
targetUser = this.pmTarget;
}
if (targetUser) {
room = null;
} else if (!room) {
- if (this.room.roomid === 'global') {
- this.connection.popup(`Your message could not be sent:\n\n${message}\n\nIt needs to be sent to a user or room.`);
- return null;
- }
// @ts-ignore excludes GlobalRoom above
room = this.room;
}
@@ -1136,9 +1137,9 @@ export class CommandContext extends MessageContext {
}
if (tagName === 'img') {
- if (this.room.settings.isPersonal && !this.user.can('lock')) {
+ if (!this.room || (this.room.settings.isPersonal && !this.user.can('lock'))) {
this.errorReply(`This tag is not allowed: <${tagContent}>`);
- this.errorReply(`Images are not allowed in personal rooms.`);
+ this.errorReply(`Images are not allowed outside of chatrooms.`);
return null;
}
if (!/width ?= ?(?:[0-9]+|"[0-9]+")/i.test(tagContent) || !/height ?= ?(?:[0-9]+|"[0-9]+")/i.test(tagContent)) {
@@ -1160,12 +1161,13 @@ export class CommandContext extends MessageContext {
}
}
if (tagName === 'button') {
- if ((this.room.settings.isPersonal || this.room.settings.isPrivate === true) && !this.user.can('lock')) {
+ if ((!this.room || this.room.settings.isPersonal || this.room.settings.isPrivate === true) && !this.user.can('lock')) {
const buttonName = / name ?= ?"([^"]*)"/i.exec(tagContent)?.[1];
const buttonValue = / value ?= ?"([^"]*)"/i.exec(tagContent)?.[1];
if (buttonName === 'send' && buttonValue?.startsWith('/msg ')) {
const [pmTarget] = buttonValue.slice(5).split(',');
- if (this.room.auth.get(toID(pmTarget)) !== '*') {
+ const auth = this.room ? this.room.auth : Users.globalAuth;
+ if (auth.get(toID(pmTarget)) !== '*') {
this.errorReply(`This button is not allowed: <${tagContent}>`);
this.errorReply(`Your scripted button can't send PMs to ${pmTarget}, because that user is not a Room Bot.`);
return null;
@@ -1248,7 +1250,7 @@ export const Chat = new class {
context: CommandContext,
message: string,
user: User,
- room: GameRoom | ChatRoom | null,
+ room: Room | null,
connection: Connection,
targetUser: User | null = null
) {
@@ -1469,7 +1471,7 @@ export const Chat = new class {
* @param user - the user that sent the message
* @param connection - the connection the user sent the message from
*/
- parse(message: string, room: Room, user: User, connection: Connection) {
+ parse(message: string, room: Room | null | undefined, user: User, connection: Connection) {
Chat.loadPlugins();
const context = new CommandContext({message, room, user, connection});
@@ -1915,7 +1917,7 @@ export type FilterWord = [RegExp, string, string, string | null, number];
export type MonitorHandler = (
this: CommandContext,
line: FilterWord,
- room: ChatRoom | GameRoom | null,
+ room: Room | null,
user: User,
message: string,
lcMessage: string,
diff --git a/server/global-types.ts b/server/global-types.ts
index 09d28f0a35..8418b5b559 100644
--- a/server/global-types.ts
+++ b/server/global-types.ts
@@ -29,7 +29,7 @@ namespace Chat {
}
// Rooms
-type GlobalRoom = Rooms.GlobalRoom;
+type GlobalRoomState = Rooms.GlobalRoomState;
type ChatRoom = Rooms.ChatRoom;
type GameRoom = Rooms.GameRoom;
type BasicRoom = Rooms.BasicRoom;
@@ -40,7 +40,7 @@ type Roomlog = Rooms.Roomlog;
type Room = Rooms.Room;
type RoomID = "" | "global" | "lobby" | "staff" | "upperstaff" | "development" | "battle" | string & {__isRoomID: true};
namespace Rooms {
- export type GlobalRoom = import('./rooms').GlobalRoom;
+ export type GlobalRoomState = import('./rooms').GlobalRoomState;
export type ChatRoom = import('./rooms').ChatRoom;
export type GameRoom = import('./rooms').GameRoom;
export type BasicRoom = import('./rooms').BasicRoom;
diff --git a/server/room-game.ts b/server/room-game.ts
index 18eeefacb5..06adfcdaa5 100644
--- a/server/room-game.ts
+++ b/server/room-game.ts
@@ -81,7 +81,7 @@ export class RoomGame {
* The room this roomgame is in. Rooms can only have one RoomGame at a time,
* which are available as `this.room.game === this`.
*/
- room: ChatRoom | GameRoom;
+ room: Room;
gameid: ID;
title: string;
allowRenames: boolean;
@@ -100,7 +100,7 @@ export class RoomGame {
* to be later. The /timer command is written to be resilient to this.
*/
timer?: {timerRequesters?: Set, start: (force?: User) => void, stop: (force?: User) => void} | NodeJS.Timer | null;
- constructor(room: ChatRoom | GameRoom) {
+ constructor(room: Room) {
this.roomid = room.roomid;
this.room = room;
this.gameid = 'game' as ID;
diff --git a/server/rooms.ts b/server/rooms.ts
index 4032c68e90..9ab7ddae4f 100644
--- a/server/rooms.ts
+++ b/server/rooms.ts
@@ -114,7 +114,7 @@ export interface RoomSettings {
noAutoTruncate?: boolean;
isMultichannel?: boolean;
}
-export type Room = GlobalRoom | GameRoom | ChatRoom;
+export type Room = GameRoom | ChatRoom;
type Poll = import('./chat-plugins/poll').Poll;
type Announcement = import('./chat-plugins/announcements').Announcement;
type RoomEvent = import('./chat-plugins/room-events').RoomEvent;
@@ -125,7 +125,7 @@ type Tournament = import('./tournaments/index').Tournament;
export abstract class BasicRoom {
roomid: RoomID;
title: string;
- readonly type: 'chat' | 'battle' | 'global';
+ readonly type: 'chat' | 'battle';
readonly users: UserTable;
/**
* Scrollback log. This is the log that's sent to users when
@@ -261,11 +261,9 @@ export abstract class BasicRoom {
* for everyone, and appears in the scrollback for new users who
* join.
*/
- add(message: string): this { throw new Error(`should be implemented by subclass`); }
- roomlog(message: string) { throw new Error(`should be implemented by subclass`); }
- modlog(message: string) { throw new Error(`should be implemented by subclass`); }
- logEntry() { throw new Error(`room.logEntry has been renamed room.roomlog`); }
- addLogMessage() { throw new Error(`room.addLogMessage has been renamed room.addByUser`); }
+ abstract add(message: string): this;
+ abstract roomlog(message: string): void;
+ abstract modlog(message: string): void;
/**
* Inserts (sanitized) HTML into the room log.
*/
@@ -474,9 +472,7 @@ export abstract class BasicRoom {
destroy() {}
}
-export class GlobalRoom extends BasicRoom {
- readonly type: 'global';
- readonly active: false;
+export class GlobalRoomState {
readonly settingsList: RoomSettings[];
readonly chatRooms: ChatRoom[];
/**
@@ -488,7 +484,6 @@ export class GlobalRoom extends BasicRoom {
*/
readonly staffAutojoinList: RoomID[];
readonly ladderIpLog: WriteStream;
- readonly users: UserTable;
readonly reportUserStatsInterval: NodeJS.Timeout;
readonly modlogStream: WriteStream;
lockdown: boolean | 'pre' | 'ddos';
@@ -496,21 +491,11 @@ export class GlobalRoom extends BasicRoom {
lastReportedCrash: number;
lastBattle: number;
lastWrittenBattle: number;
- userCount: number;
maxUsers: number;
maxUsersDate: number;
formatList: string;
- readonly game: null;
- readonly battle: null;
- readonly tour: null;
-
- constructor(roomid: RoomID) {
- if (roomid !== 'global') throw new Error(`The global room's room ID must be 'global'`);
- super(roomid);
-
- this.type = 'global';
- this.active = false;
+ constructor() {
this.settingsList = [];
try {
this.settingsList = require('../config/chatrooms.json');
@@ -580,8 +565,6 @@ export class GlobalRoom extends BasicRoom {
);
// init users
- this.users = Object.create(null);
- this.userCount = 0; // cache of `size(this.users)`
this.maxUsers = 0;
this.maxUsersDate = 0;
this.lockdown = false;
@@ -637,7 +620,7 @@ export class GlobalRoom extends BasicRoom {
}
void LoginServer.request('updateuserstats', {
date: Date.now(),
- users: this.userCount,
+ users: Users.onlineCount,
});
}
@@ -753,7 +736,7 @@ export class GlobalRoom extends BasicRoom {
official: [],
pspl: [],
chat: [],
- userCount: this.userCount,
+ userCount: Users.onlineCount,
battleCount: this.battleCount,
};
for (const room of this.chatRooms) {
@@ -779,18 +762,8 @@ export class GlobalRoom extends BasicRoom {
}
return roomsData;
}
- checkModjoin(user: User) {
- return true;
- }
- isMuted(user: User) {
- return undefined;
- }
- send(message: string) {
- Sockets.roomBroadcast(this.roomid, message);
- }
- add(message: string) {
- // TODO: make sure this never happens
- return this;
+ sendAll(message: string) {
+ Sockets.roomBroadcast('', message);
}
addChatRoom(title: string) {
const id = toID(title) as RoomID;
@@ -896,7 +869,7 @@ export class GlobalRoom extends BasicRoom {
checkAutojoin(user: User, connection?: Connection) {
if (!user.named) return;
for (let [i, staffAutojoin] of this.staffAutojoinList.entries()) {
- const room = Rooms.get(staffAutojoin) as ChatRoom | GameRoom;
+ const room = Rooms.get(staffAutojoin);
if (!room) {
this.staffAutojoinList.splice(i, 1);
i--;
@@ -921,34 +894,12 @@ export class GlobalRoom extends BasicRoom {
}
}
}
- onConnect(user: User, connection: Connection) {
+ handleConnect(user: User, connection: Connection) {
connection.send(user.getUpdateuserText() + '\n' + this.configRankList + this.formatListText);
- }
- onJoin(user: User, connection: Connection) {
- if (!user) return false; // ???
- if (this.users[user.id]) return user;
-
- this.users[user.id] = user;
- if (++this.userCount > this.maxUsers) {
- this.maxUsers = this.userCount;
+ if (Users.users.size > this.maxUsers) {
+ this.maxUsers = Users.users.size;
this.maxUsersDate = Date.now();
}
-
- return user;
- }
- onRename(user: User, oldid: ID, joining: boolean) {
- delete this.users[oldid];
- this.users[user.id] = user;
- return user;
- }
- onLeave(user: User) {
- if (!user) return; // ...
- if (!(user.id in this.users)) {
- Monitor.crashlog(new Error(`user ${user.id} already left`));
- return;
- }
- delete this.users[user.id];
- this.userCount--;
}
startLockdown(err: Error | null = null, slow = false) {
if (this.lockdown && err) return;
@@ -1681,8 +1632,8 @@ export class GameRoom extends BasicChatRoom {
}
function getRoom(roomid?: string | Room) {
- if (roomid && (roomid as Room).roomid) return roomid as Room;
- return Rooms.rooms.get(roomid as RoomID);
+ if (typeof roomid === 'string') return Rooms.rooms.get(roomid as RoomID);
+ return roomid as Room;
}
export const Rooms = {
@@ -1813,11 +1764,11 @@ export const Rooms = {
battleModlogStream: FS('logs/modlog/modlog_battle.txt').createAppendStream(),
groupchatModlogStream: FS('logs/modlog/modlog_groupchat.txt').createAppendStream(),
- global: null! as GlobalRoom,
+ global: null! as GlobalRoomState,
lobby: null as ChatRoom | null,
BasicRoom,
- GlobalRoom,
+ GlobalRoomState,
GameRoom,
ChatRoom: BasicChatRoom,
@@ -1836,8 +1787,4 @@ export const Rooms = {
// initialize
-Monitor.notice("NEW GLOBAL: global");
-
-Rooms.global = new GlobalRoom('global');
-
-Rooms.rooms.set('global', Rooms.global);
+Rooms.global = new GlobalRoomState();
diff --git a/server/sockets.ts b/server/sockets.ts
index 8e92d350b9..cc4eabd727 100644
--- a/server/sockets.ts
+++ b/server/sockets.ts
@@ -458,9 +458,11 @@ export class ServerStream extends Streams.ObjectReadWriteStream {
case '#':
// #roomid, message
// message to all connections in room
+ // #, message
+ // message to all connections
nlLoc = data.indexOf('\n');
roomid = data.substr(1, nlLoc - 1) as RoomID;
- room = this.rooms.get(roomid);
+ room = roomid ? this.rooms.get(roomid) : this.sockets;
if (!room) return;
message = data.substr(nlLoc + 1);
for (const curSocket of room.values()) curSocket.write(message);
diff --git a/server/tournaments/index.ts b/server/tournaments/index.ts
index 9af670d822..85fca1f194 100644
--- a/server/tournaments/index.ts
+++ b/server/tournaments/index.ts
@@ -1121,7 +1121,7 @@ function createTournamentGenerator(
return new TourGenerator(modifier || '');
}
function createTournament(
- room: ChatRoom | GameRoom, formatId: string | undefined, generator: string | undefined, playerCap: string | undefined,
+ room: Room, formatId: string | undefined, generator: string | undefined, playerCap: string | undefined,
isRated: boolean, generatorMod: string | undefined, name: string | undefined, output: CommandContext
) {
if (room.type !== 'chat') {
@@ -1241,10 +1241,10 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
if (Config.tourdefaultplayercap && tournament.playerCap > Config.tourdefaultplayercap) {
Monitor.log(`[TourMonitor] Room ${tournament.room.roomid} starting a tour over default cap (${tournament.playerCap})`);
}
- this.room.send(`|tournament|update|{"playerCap": "${playerCap}"}`);
+ this.room!.send(`|tournament|update|{"playerCap": "${playerCap}"}`);
} else if (tournament.playerCap && !playerCap) {
tournament.playerCap = 0;
- this.room.send(`|tournament|update|{"playerCap": "${playerCap}"}`);
+ this.room!.send(`|tournament|update|{"playerCap": "${playerCap}"}`);
}
const capNote = (tournament.playerCap ? ' with a player cap of ' + tournament.playerCap : '');
this.privateModAction(`(${user.name} set tournament type to ${generator.name + capNote}.)`);
@@ -1292,7 +1292,7 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
this.modlog('TOUR PLAYERCAP', null, tournament.playerCap.toString());
this.sendReply(`Tournament cap set to ${tournament.playerCap}.`);
}
- this.room.send(`|tournament|update|{"playerCap": "${tournament.playerCap}"}`);
+ this.room!.send(`|tournament|update|{"playerCap": "${tournament.playerCap}"}`);
},
end: 'delete',
stop: 'delete',
@@ -1317,7 +1317,9 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
return this.errorReply("The custom rules cannot be changed once the tournament has started.");
}
if (tournament.setCustomRules(params, this)) {
- this.room.addRaw(`This tournament includes:
${tournament.getCustomRules()}
`);
+ this.room!.addRaw(
+ `This tournament includes:
${tournament.getCustomRules()}
`
+ );
this.privateModAction(`(${user.name} updated the tournament's custom rules.)`);
this.modlog('TOUR RULES', null, tournament.customRules.join(', '));
}
@@ -1336,10 +1338,10 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
tournament.fullFormat = tournament.baseFormat;
if (tournament.name === tournament.getDefaultCustomName()) {
tournament.name = tournament.baseFormat;
- this.room.send(`|tournament|update|${JSON.stringify({format: tournament.name})}`);
+ this.room!.send(`|tournament|update|${JSON.stringify({format: tournament.name})}`);
tournament.update();
}
- this.room.addRaw(`The tournament's custom rules were cleared.`);
+ this.room!.addRaw(`The tournament's custom rules were cleared.`);
this.privateModAction(`(${user.name} cleared the tournament's custom rules.)`);
this.modlog('TOUR CLEARRULES');
},
@@ -1357,7 +1359,7 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
}
if (name.includes('|')) return this.errorReply("The tournament's name cannot include the | symbol.");
tournament.name = name;
- this.room.send(`|tournament|update|${JSON.stringify({format: tournament.name})}`);
+ this.room!.send(`|tournament|update|${JSON.stringify({format: tournament.name})}`);
this.privateModAction(`(${user.name} set the tournament's name to ${tournament.name}.)`);
this.modlog('TOUR NAME', null, tournament.name);
tournament.update();
@@ -1366,7 +1368,7 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
clearname(tournament, user) {
if (tournament.name === tournament.baseFormat) return this.errorReply("The tournament does not have a name.");
tournament.name = tournament.baseFormat;
- this.room.send(`|tournament|update|${JSON.stringify({format: tournament.name})}`);
+ this.room!.send(`|tournament|update|${JSON.stringify({format: tournament.name})}`);
this.privateModAction(`(${user.name} cleared the tournament's name.)`);
this.modlog('TOUR CLEARNAME');
tournament.update();
@@ -1376,7 +1378,7 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
begin: 'start',
start(tournament, user) {
if (tournament.startTournament(this)) {
- this.room.sendMods(`(${user.name} started the tournament.)`);
+ this.room!.sendMods(`(${user.name} started the tournament.)`);
}
},
dq: 'disqualify',
@@ -1423,7 +1425,7 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
return this.errorReply("The tournament is already set to autostart when the player cap is reached.");
}
tournament.autostartcap = true;
- this.room.add(`The tournament will start once ${tournament.playerCap} players have joined.`);
+ this.room!.add(`The tournament will start once ${tournament.playerCap} players have joined.`);
this.privateModAction(`(The tournament was set to autostart when the player cap is reached by ${user.name})`);
this.modlog('TOUR AUTOSTART', null, 'when playercap is reached');
}
@@ -1485,14 +1487,14 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
if (tournament.scouting) return this.errorReply("Scouting for this tournament is already set to allowed.");
tournament.scouting = true;
tournament.modjoin = false;
- this.room.add('|tournament|scouting|allow');
+ this.room!.add('|tournament|scouting|allow');
this.privateModAction(`(The tournament was set to allow scouting by ${user.name})`);
this.modlog('TOUR SCOUT', null, 'allow');
} else if (this.meansNo(option) || option === 'disallow' || option === 'disallowed') {
if (!tournament.scouting) return this.errorReply("Scouting for this tournament is already disabled.");
tournament.scouting = false;
tournament.modjoin = true;
- this.room.add('|tournament|scouting|disallow');
+ this.room!.add('|tournament|scouting|disallow');
this.privateModAction(`(The tournament was set to disallow scouting by ${user.name})`);
this.modlog('TOUR SCOUT', null, 'disallow');
} else {
@@ -1513,13 +1515,13 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
if (this.meansYes(option) || option === 'allow' || option === 'allowed') {
if (tournament.modjoin) return this.errorReply("Modjoining is already allowed for this tournament.");
tournament.modjoin = true;
- this.room.add("Modjoining is now allowed (Players can modjoin their tournament battles).");
+ this.room!.add("Modjoining is now allowed (Players can modjoin their tournament battles).");
this.privateModAction(`(The tournament was set to allow modjoin by ${user.name})`);
this.modlog('TOUR MODJOIN', null, option);
} else if (this.meansNo(option) || option === 'disallow' || option === 'disallowed') {
if (!tournament.modjoin) return this.errorReply("Modjoining is already not allowed for this tournament.");
tournament.modjoin = false;
- this.room.add("Modjoining is now banned (Players cannot modjoin their tournament battles).");
+ this.room!.add("Modjoining is now banned (Players cannot modjoin their tournament battles).");
this.privateModAction(`(The tournament was set to disallow modjoin by ${user.name})`);
this.modlog('TOUR MODJOIN', null, option);
} else {
@@ -1530,12 +1532,12 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
const option = params[0] || 'on';
if (this.meansYes(option)) {
tournament.forcePublic = true;
- this.room.add('Tournament battles forced public: ON');
+ this.room!.add('Tournament battles forced public: ON');
this.privateModAction(`(Tournament public battles were turned ON by ${user.name})`);
this.modlog('TOUR FORCEPUBLIC', null, 'ON');
} else if (this.meansNo(option) || option === 'stop') {
tournament.forcePublic = false;
- this.room.add('Tournament battles forced public: OFF');
+ this.room!.add('Tournament battles forced public: OFF');
this.privateModAction(`(Tournament public battles were turned OFF by ${user.name})`);
this.modlog('TOUR FORCEPUBLIC', null, 'OFF');
} else {
@@ -1546,12 +1548,12 @@ const tourCommands: {basic: TourCommands, creation: TourCommands, moderation: To
const option = params.length ? params[0].toLowerCase() : 'on';
if (this.meansYes(option)) {
tournament.forceTimer = true;
- this.room.add('Forcetimer is now on for the tournament.');
+ this.room!.add('Forcetimer is now on for the tournament.');
this.privateModAction(`(The timer was turned on for the tournament by ${user.name})`);
this.modlog('TOUR FORCETIMER', null, 'ON');
} else if (this.meansNo(option) || option === 'stop') {
tournament.forceTimer = false;
- this.room.add('Forcetimer is now off for the tournament.');
+ this.room!.add('Forcetimer is now off for the tournament.');
this.privateModAction(`(The timer was turned off for the tournament by ${user.name})`);
this.modlog('TOUR FORCETIMER', null, 'OFF');
} else {
@@ -1714,9 +1716,9 @@ export const commands: ChatCommands = {
const punishment: [string, ID, number, string] =
['TOURBAN', targetUserid, Date.now() + TOURBAN_DURATION, reason];
if (targetUser) {
- Punishments.roomPunish(this.room, targetUser, punishment);
+ Punishments.roomPunish(this.room!, targetUser, punishment);
} else {
- Punishments.roomPunishName(this.room, targetUserid, punishment);
+ Punishments.roomPunishName(this.room!, targetUserid, punishment);
}
room.getGame(Tournament)?.removeBannedUser(targetUserid);
@@ -1734,7 +1736,7 @@ export const commands: ChatCommands = {
if (!Tournament.checkBanned(room, targetUserid)) return this.errorReply("This user isn't banned from tournaments.");
- if (targetUser) { Punishments.roomUnpunish(this.room, targetUserid, 'TOURBAN', false); }
+ if (targetUser) { Punishments.roomUnpunish(this.room!, targetUserid, 'TOURBAN', false); }
this.privateModAction(`${targetUser ? targetUser.name : targetUserid} was unbanned from joining tournaments by ${user.name}.`);
this.modlog('TOUR UNBAN', targetUser, null, {noip: 1, noalts: 1});
} else {
diff --git a/server/users.ts b/server/users.ts
index f25347b9dd..7e2c0e2887 100644
--- a/server/users.ts
+++ b/server/users.ts
@@ -391,6 +391,7 @@ export class User extends Chat.MessageContext {
this.avatar = DEFAULT_TRAINER_SPRITES[Math.floor(Math.random() * DEFAULT_TRAINER_SPRITES.length)];
this.connected = true;
+ Users.onlineCount++;
if (connection.user) connection.user = this;
this.connections = [connection];
@@ -457,7 +458,7 @@ export class User extends Chat.MessageContext {
sendTo(roomid: RoomID | BasicRoom | null, data: string) {
if (roomid && typeof roomid !== 'string') roomid = (roomid as BasicRoom).roomid;
- if (roomid && roomid !== 'global' && roomid !== 'lobby') data = `>${roomid}\n${data}`;
+ if (roomid && roomid !== 'lobby') data = `>${roomid}\n${data}`;
for (const connection of this.connections) {
if (roomid && !connection.inRooms.has(roomid)) continue;
connection.send(data);
@@ -479,7 +480,7 @@ export class User extends Chat.MessageContext {
const lockedSymbol = (punishgroups.locked && punishgroups.locked.symbol || '\u203d');
return lockedSymbol + this.name;
}
- if (roomid && roomid !== 'global') {
+ if (roomid) {
const room = Rooms.get(roomid);
if (!room) {
throw new Error(`Room doesn't exist: ${roomid}`);
@@ -946,7 +947,10 @@ export class User extends Chat.MessageContext {
mergeConnection(connection: Connection) {
// the connection has changed name to this user's username, and so is
// being merged into this account
- this.connected = true;
+ if (!this.connected) {
+ this.connected = true;
+ Users.onlineCount++;
+ }
if (connection.connectedAt > this.lastConnected) {
this.lastConnected = connection.connectedAt;
}
@@ -1079,7 +1083,9 @@ export class User extends Chat.MessageContext {
return removed;
}
markDisconnected() {
+ if (!this.connected) return;
this.connected = false;
+ Users.onlineCount--;
this.lastDisconnected = Date.now();
if (!this.registered) {
// for "safety"
@@ -1217,12 +1223,7 @@ export class User extends Chat.MessageContext {
if (!room) throw new Error(`Room not found: ${roomid}`);
if (!connection) {
for (const curConnection of this.connections) {
- // only join full clients, not pop-out single-room
- // clients
- // (...no, pop-out rooms haven't been implemented yet)
- if (curConnection.inRooms.has('global')) {
- this.joinRoom(room, curConnection);
- }
+ this.joinRoom(room, curConnection);
}
return;
}
@@ -1295,7 +1296,7 @@ export class User extends Chat.MessageContext {
* The user says message in room.
* Returns false if the rest of the user's messages should be discarded.
*/
- chat(message: string, room: Room, connection: Connection) {
+ chat(message: string, room: Room | null, connection: Connection) {
const now = Date.now();
if (message.startsWith('/cmd userdetails') || message.startsWith('>> ') || this.isSysop) {
@@ -1318,10 +1319,10 @@ export class User extends Chat.MessageContext {
);
return false;
} else {
- this.chatQueue.push([message, room.roomid, connection]);
+ this.chatQueue.push([message, room ? room.roomid : '', connection]);
}
} else if (now < this.lastChatMessage + throttleDelay) {
- this.chatQueue = [[message, room.roomid, connection]];
+ this.chatQueue = [[message, room ? room.roomid : '', connection]];
this.startChatQueue(throttleDelay - (now - this.lastChatMessage));
} else {
this.lastChatMessage = now;
@@ -1366,12 +1367,12 @@ export class User extends Chat.MessageContext {
this.lastChatMessage = new Date().getTime();
const room = Rooms.get(roomid);
- if (room) {
+ if (room || !roomid) {
Monitor.activeIp = connection.ip;
Chat.parse(message, room, this, connection);
Monitor.activeIp = null;
} else {
- // room is expired, do nothing
+ // room no longer exists; do nothing
}
const throttleDelay = this.trusted ? THROTTLE_DELAY_TRUSTED : THROTTLE_DELAY;
@@ -1516,7 +1517,7 @@ function socketConnect(
}
});
- user.joinRoom('global', connection);
+ Rooms.global.handleConnect(user, connection);
}
function socketDisconnect(worker: StreamWorker, workerid: number, socketid: string) {
const id = '' + workerid + '-' + socketid;
@@ -1558,11 +1559,10 @@ function socketReceive(worker: StreamWorker, workerid: number, socketid: string,
if (!user) return;
// The client obviates the room id when sending messages to Lobby by default
- const roomId = message.substr(0, pipeIndex) || (Rooms.lobby || Rooms.global).roomid;
+ const roomId = message.slice(0, pipeIndex) || (Rooms.lobby && 'lobby') || '';
message = message.slice(pipeIndex + 1);
- const room = Rooms.get(roomId);
- if (!room) return;
+ const room = Rooms.get(roomId) || null;
const multilineMessage = Chat.multiLinePattern.test(message);
if (multilineMessage) {
user.chat(multilineMessage, room, connection);
@@ -1571,7 +1571,8 @@ function socketReceive(worker: StreamWorker, workerid: number, socketid: string,
const lines = message.split('\n');
if (!lines[lines.length - 1]) lines.pop();
- const maxLineCount = (user.isStaff || room.auth.isStaff(user.id)) ?
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
+ const maxLineCount = (user.isStaff || (room && room.auth.isStaff(user.id))) ?
THROTTLE_MULTILINE_WARN_STAFF : THROTTLE_MULTILINE_WARN;
if (lines.length > maxLineCount) {
connection.popup(`You're sending too many lines at once. Try using a paste service like [[Pastebin]].`);
@@ -1603,6 +1604,7 @@ export const Users = {
merge,
users,
prevUsers,
+ onlineCount: 0,
get: getUser,
getExact: getExactUser,
findUsers,
diff --git a/test/server/chat-plugins/trivia.js b/test/server/chat-plugins/trivia.js
index d0f907bed5..3e2f643eea 100644
--- a/test/server/chat-plugins/trivia.js
+++ b/test/server/chat-plugins/trivia.js
@@ -14,9 +14,7 @@ let NumberModeTrivia;
function makeUser(name, connection) {
const user = new User(connection);
user.forceRename(name, true);
- user.connected = true;
Users.users.set(user.id, user);
- user.joinRoom('global', connection);
user.joinRoom('trivia', connection);
return user;
}
diff --git a/test/users-utils.js b/test/users-utils.js
index 7e209f592e..d30e4f52e9 100644
--- a/test/users-utils.js
+++ b/test/users-utils.js
@@ -164,7 +164,6 @@ function createUser(connection) {
if (!connection) connection = createConnection();
const user = new Users.User(connection);
- user.joinRoom('global', connection);
connection.user = user;
return user;