Skip to content

Commit

Permalink
Support renaming rooms (#6295)
Browse files Browse the repository at this point in the history
  • Loading branch information
thejetou authored Feb 21, 2020
1 parent f89f44f commit 34e215c
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 2 deletions.
12 changes: 12 additions & 0 deletions lib/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ class FSPath {
return fs.readFileSync(this.path, options) as Buffer;
}

exists(): Promise<boolean> {
return new Promise(resolve => {
fs.exists(this.path, exists => {
resolve(exists);
});
});
}

existsSync() {
return fs.existsSync(this.path);
}

readIfExists(): Promise<string> {
return new Promise((resolve, reject) => {
fs.readFile(this.path, 'utf8', (err, data) => {
Expand Down
23 changes: 23 additions & 0 deletions server/chat-commands/room-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,29 @@ export const commands: ChatCommands = {
`/deletegroupchat - Deletes the current room, if it's a groupchat. Requires: ★ # & ~`,
],

rename(target, room) {
if (!this.can('declare')) return;
if (room.minorActivity || room.game || room.tour) {
return this.errorReply("Cannot rename room when there's a tour/game/poll/announcement running.");
}
if (room.battle) {
return this.errorReply("Battle rooms cannot be renamed.");
}

const roomid = toID(target) as RoomID;
const roomtitle = target;
// `,` is a delimiter used by a lot of /commands
// `|` and `[` are delimiters used by the protocol
// `-` has special meaning in roomids
if (roomtitle.includes(',') || roomtitle.includes('|') || roomtitle.includes('[') || roomtitle.includes('-')) {
return this.errorReply("Room titles can't contain any of: ,|[-");
}
if (roomid.length > MAX_CHATROOM_ID_LENGTH) return this.errorReply("The given room title is too long.");
if (Rooms.search(roomtitle)) return this.errorReply(`The room '${roomtitle}' already exists.`);
return room.rename(roomtitle);
},
renamehelp: [`/rename [new title] - Renames the current room to [new title]. Requires & ~.`],

hideroom: 'privateroom',
hiddenroom: 'privateroom',
secretroom: 'privateroom',
Expand Down
2 changes: 1 addition & 1 deletion server/room-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export class RoomGamePlayer {
*/
export class RoomGame {
readonly roomid: RoomID;
readonly room: ChatRoom | GameRoom;
room: ChatRoom | GameRoom;
gameid: ID;
title: string;
allowRenames: boolean;
Expand Down
33 changes: 32 additions & 1 deletion server/roomlogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {FS} from '../lib/fs';
* It contains (nearly) everything.
*/
export class Roomlog {
readonly roomid: RoomID;
/**
* Battle rooms are multichannel, which means their logs are split
* into four channels, public, p1, p2, full.
Expand All @@ -43,6 +42,7 @@ export class Roomlog {
* Chat rooms include timestamps.
*/
readonly logTimes: boolean;
roomid: RoomID;
/**
* Scrollback log
*/
Expand Down Expand Up @@ -218,6 +218,37 @@ export class Roomlog {
if (!this.modlogStream) return;
this.modlogStream.write('[' + (new Date().toJSON()) + '] ' + message + '\n');
}
async rename(newID: RoomID) {
const modlogPath = `logs/modlog`;
const roomlogPath = `logs/chat`;
const modlogStreamExisted = !!this.modlogStream;
const roomlogStreamExisted = !!this.roomlogStream;
await this.destroy();
await Promise.all([
FS(modlogPath + `/modlog_${this.roomid}.txt`).exists(),
FS(roomlogPath + `/${this.roomid}`).exists(),
FS(modlogPath + `/modlog_${newID}.txt`).exists(),
FS(roomlogPath + `/${newID}`).exists(),
]).then(([modlogExists, roomlogExists, newModlogExists, newRoomlogExists]) => {
return Promise.all([
modlogExists && !newModlogExists
? FS(modlogPath + `/modlog_${this.roomid}.txt`).rename(modlogPath + `/modlog_${newID}.txt`)
: undefined,
roomlogExists && !newRoomlogExists
? FS(roomlogPath + `/${this.roomid}`).rename(roomlogPath + `/${newID}`)
: undefined,
]);
});
this.roomid = newID;
Roomlogs.roomlogs.set(newID, this);
if (modlogStreamExisted) {
this.setupModlogStream();
}
if (roomlogStreamExisted) {
await this.setupRoomlogStream(true);
}
return true;
}
static async rollLogs() {
if (Roomlogs.rollLogTimer === true) return;
if (Roomlogs.rollLogTimer) {
Expand Down
43 changes: 43 additions & 0 deletions server/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,49 @@ export class BasicChatRoom extends BasicRoom {
if (this.game && this.game.onLeave) this.game.onLeave(user);
return true;
}
/**
* @param newID Add this param if the roomid is different from `toID(newTitle)`
*/
async rename(newTitle: string, newID?: RoomID) {
if (!newID) newID = toID(newTitle) as RoomID;
if (this.game || this.tour) return;

const oldID = this.roomid;
this.roomid = newID;
this.title = newTitle;
Rooms.rooms.delete(oldID);
Rooms.rooms.set(newID, this as ChatRoom);

for (const user of Object.values(this.users)) {
user.inRooms.delete(oldID);
user.inRooms.add(newID);
for (const connection of user.connections) {
connection.inRooms.delete(oldID);
connection.inRooms.add(newID);
Sockets.roomRemove(connection.worker, oldID, connection.socketid);
Sockets.roomAdd(connection.worker, newID, connection.socketid);
}
user.send(`>${oldID}\n|noinit|rename|${newID}|${newTitle}`);
}

if (this.parent && this.parent.subRooms) {
this.parent.subRooms.delete(oldID);
this.parent.subRooms.set(newID, this as ChatRoom);
}

if (this.subRooms) {
for (const subRoom of this.subRooms.values()) {
subRoom.parent = this as ChatRoom;
}
}

if (this.chatRoomData) {
this.chatRoomData.title = newTitle;
Rooms.global.writeChatRoomData();
}

return this.log.rename(newID);
}
destroy() {
// deallocate ourself

Expand Down
59 changes: 59 additions & 0 deletions test/server/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,63 @@ describe('Rooms features', function () {
assert.equal(room.getAuth(roomStaff), '+', 'after being promoted by an administrator');
});
});

describe("ChatRoom", function () {
describe("#rename", function () {
let room;
let parent;
let subroom;
afterEach(function () {
for (const user of Users.users.values()) {
user.disconnectAll();
user.destroy();
}
const rooms = [room, parent, subroom];
for (const room of rooms) {
if (room) {
room.destroy();
}
}
});
it("should rename its roomid and title", async function () {
room = Rooms.createChatRoom("test", "Test");
await room.rename("Test2");
assert.strictEqual(room.roomid, "test2");
assert.strictEqual(room.title, "Test2");
});

it("should rename its key in Rooms.rooms", async function () {
room = Rooms.createChatRoom("test", "Test");
await room.rename("Test2");
assert.strictEqual(Rooms.rooms.has("test"), false);
assert.strictEqual(Rooms.rooms.has("test2"), true);
});

it("should move the users and their connections", async function () {
room = Rooms.createChatRoom("test", "Test");
const user = new User();
user.joinRoom(room);
await room.rename("Test2");
assert.strictEqual(user.inRooms.has("test"), false);
assert.strictEqual(user.inRooms.has("test2"), true);
assert.strictEqual(user.connections[0].inRooms.has("test"), false);
assert.strictEqual(user.connections[0].inRooms.has("test2"), true);
});

it("should rename their parents subroom reference", async function () {
parent = Rooms.createChatRoom("parent", "Parent");
subroom = Rooms.createChatRoom("subroom", "Subroom", {parentid: "parent"});
await subroom.rename("TheSubroom");
assert.strictEqual(parent.subRooms.has("subroom"), false);
assert.strictEqual(parent.subRooms.has("thesubroom"), true);
});

it("should rename their subrooms parent reference", async function () {
parent = Rooms.createChatRoom("parent", "Parent");
subroom = Rooms.createChatRoom("subroom", "Subroom", {parentid: "parent"});
await parent.rename("TheParent");
assert.strictEqual(subroom.parent, parent);
});
});
});
});

0 comments on commit 34e215c

Please sign in to comment.