Skip to content

Commit ff79d8e

Browse files
committed
refactor: Assorted bugs and comments
1 parent bc35bac commit ff79d8e

8 files changed

Lines changed: 234 additions & 168 deletions

File tree

src/cache/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,17 @@ export const PSCommands: { [key: string]: PSCommand & { path: string } } = {};
2121
*/
2222
export const PSAliases: { [key: string]: string } = {};
2323
export const PSAltCache: Partial<{ [key: string]: { from: string; to: string; at: Date } }> = {};
24-
export const PSJoinphraseCache: Partial<{ [key: string]: { id: string; phrase: string } }> = {};
24+
export const PSJoinphraseCache: Partial<{
25+
[room: string]: Partial<{
26+
[userId: string]: {
27+
id: string;
28+
phrase: string;
29+
username: string;
30+
messageCount: number; // messages since last JP
31+
lastTime: number; // epoch timestamp of last JP
32+
};
33+
}>;
34+
}> = {};
2535
export const PSSeenCache: Partial<{ [key: string]: { id: string; name: string; at: Date; seenIn: string[] } }> = {};
2636
export const PSCronJobs: { manager: PSCronJobManager | null } = { manager: null };
2737

src/database/joinphrases.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import mongoose from 'mongoose';
22

3-
import { username } from '@/config/ps';
43
import { IS_ENABLED } from '@/enabled';
54
import { toId } from '@/utils/toId';
65

@@ -26,7 +25,7 @@ const schema = new mongoose.Schema<Model>({
2625
},
2726
userId: {
2827
type: String,
29-
default: toId(username),
28+
default: ({ username }: Model) => toId(username),
3029
},
3130
roomId: {
3231
type: String,
@@ -48,17 +47,20 @@ const schema = new mongoose.Schema<Model>({
4847

4948
const model = mongoose.model<Model>('joinphrase', schema, 'joinphrases', { overwriteModels: true });
5049

51-
export async function addJoinphrase(username: string, roomId: string, phrase: string, by: string): Promise<Model | null> {
50+
export async function setJoinphrase(username: string, roomId: string, phrase: string, by: string): Promise<Model | null> {
5251
if (!IS_ENABLED.DB) return null;
5352
const userId = toId(username);
54-
return model.create({
55-
id: `${userId}-${roomId}`,
56-
username,
57-
userId,
58-
roomId: roomId,
59-
phrase,
60-
addedBy: by,
61-
});
53+
return model.findOneAndUpdate(
54+
{
55+
id: `${userId}-${roomId}`,
56+
username,
57+
userId,
58+
roomId,
59+
phrase,
60+
addedBy: by,
61+
},
62+
{ upsert: true, new: true }
63+
);
6264
}
6365

6466
export async function getJoinphrase(username: string, roomId: string): Promise<{ phrase: string } | null> {
@@ -68,9 +70,9 @@ export async function getJoinphrase(username: string, roomId: string): Promise<{
6870
return await model.findOne({ id }, { phrase: 1, _id: 0 }).lean();
6971
}
7072

71-
export async function fetchAllJoinphrases(): Promise<Model[]> {
73+
export async function fetchAllJoinphrases(roomId: string | null): Promise<Model[]> {
7274
if (!IS_ENABLED.DB) return [];
73-
return model.find({}).lean();
75+
return model.find(roomId ? { roomId } : {}).lean();
7476
}
7577

7678
export async function deleteJoinphrase(username: string, roomId: string): Promise<Model | null> {
@@ -79,9 +81,7 @@ export async function deleteJoinphrase(username: string, roomId: string): Promis
7981
const id = `${toId(username)}-${roomId}`;
8082
const toDelete = await model.findOne({ id });
8183

82-
if (!toDelete) {
83-
return null;
84-
}
84+
if (!toDelete) return null;
8585
await toDelete.deleteOne();
8686
return toDelete.toObject();
8787
}

src/ps/commands/joinphrases.ts

Lines changed: 0 additions & 103 deletions
This file was deleted.

src/ps/commands/joinphrases.tsx

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { toRoomID } from 'ps-client/tools';
2+
3+
import { deleteJoinphrase, fetchAllJoinphrases, getJoinphrase, setJoinphrase } from '@/database/joinphrases';
4+
import { MAX_MESSAGE_LENGTH } from '@/ps/constants';
5+
import { ChatError } from '@/utils/chatError';
6+
import { Username } from '@/utils/components';
7+
8+
import type { NoTranslate, PSMessageTranslated, ToTranslate } from '@/i18n/types';
9+
import type { PSCommand } from '@/types/chat';
10+
11+
function validateJoinphrase(phrase: string): void {
12+
if (!phrase) throw new ChatError('A joinphrase cannot be empty!' as ToTranslate);
13+
if (phrase.length > MAX_MESSAGE_LENGTH)
14+
throw new ChatError(`A joinphrase cannot be longer than ${MAX_MESSAGE_LENGTH} characters!` as ToTranslate);
15+
16+
// Security checks
17+
if (phrase.startsWith('!') || phrase.startsWith('/')) {
18+
const VALID_COMMANDS = ['!dt', '/me'];
19+
if (!VALID_COMMANDS.some(cmd => phrase.startsWith(cmd + ' '))) {
20+
throw new ChatError('A joinphrase cannot start with a command!' as ToTranslate);
21+
}
22+
}
23+
}
24+
25+
async function getRoom(message: PSMessageTranslated, arg: string): Promise<string> {
26+
if (message.type === 'chat') return message.target.roomid;
27+
if (arg) return toRoomID(arg);
28+
const reply = await message.target.waitFor(msg => msg.content.length > 0 && !!msg.parent.getRoom(toRoomID(msg.content)));
29+
if (!reply) throw new ChatError('No room provided!' as ToTranslate);
30+
return toRoomID(reply.content);
31+
}
32+
33+
export const command: PSCommand = {
34+
name: 'joinphrase',
35+
help: 'Joinphrases module! Joinphrases are messages that are sent when a user joins a room.',
36+
perms: ['room', 'driver'],
37+
syntax: 'CMD',
38+
aliases: ['jp', 'joinphrases'],
39+
categories: ['utility'],
40+
extendedAliases: {
41+
addjp: ['jp', 'new'],
42+
addjoinphrase: ['jp', 'new'],
43+
ajp: ['jp', 'new'],
44+
deletejp: ['jp', 'delete'],
45+
deletejoinphrase: ['jp', 'delete'],
46+
djp: ['jp', 'delete'],
47+
removejp: ['jp', 'delete'],
48+
remjp: ['jp', 'delete'],
49+
ejp: ['jp', 'edit'],
50+
editjoinphrase: ['jp', 'edit'],
51+
getjp: ['jp', 'get'],
52+
showjp: ['jp', 'get'],
53+
displayjp: ['jp', 'get'],
54+
vjp: ['jp', 'get'],
55+
viewjp: ['jp', 'get'],
56+
},
57+
children: {
58+
help: {
59+
name: 'help',
60+
help: 'Shows the help for the joinphrases command',
61+
aliases: ['h'],
62+
syntax: 'CMD',
63+
async run({ run }) {
64+
run('help jp');
65+
},
66+
},
67+
add: {
68+
name: 'add',
69+
help: 'Adds a new joinphrase for a given user',
70+
flags: { allowPMs: false },
71+
syntax: 'CMD [user], [joinphrase]',
72+
aliases: ['new', 'a', 'n'],
73+
async run({ message, arg, $T }) {
74+
if (!arg) throw new ChatError($T('INVALID_ARGUMENTS'));
75+
const [username, phrase] = arg.lazySplit(/\s*,\s*/, 1).map(s => s.trim());
76+
if (!phrase) throw new ChatError($T('INVALID_ARGUMENTS'));
77+
const targetUser = username.trim();
78+
if (await getJoinphrase(targetUser, message.target.id)) {
79+
throw new ChatError(`${targetUser} already has a joinphrase in ${message.target.title}...` as ToTranslate);
80+
}
81+
validateJoinphrase(phrase);
82+
await setJoinphrase(targetUser, message.target.id, phrase, message.author.name);
83+
message.reply('Joinphrase Added!' as ToTranslate);
84+
},
85+
},
86+
view: {
87+
name: 'view',
88+
help: 'Displays a given joinphrase',
89+
syntax: 'CMD [user]',
90+
flags: { allowPMs: false },
91+
aliases: ['show', 'display', 'get'],
92+
async run({ message, arg, $T }) {
93+
if (!arg) throw new ChatError($T('INVALID_ARGUMENTS'));
94+
const targetUser = arg.trim();
95+
96+
const { phrase } = (await getJoinphrase(targetUser, message.target.id)) ?? {};
97+
if (!phrase) throw new ChatError(`${targetUser} does not have a joinphrase in ${message.target.title}...` as ToTranslate);
98+
99+
message.privateReply(`${phrase}` as NoTranslate);
100+
},
101+
},
102+
delete: {
103+
name: 'delete',
104+
help: "Deletes a user's joinphrase",
105+
syntax: 'CMD [user]',
106+
flags: { allowPMs: false },
107+
aliases: ['del', 'remove', 'rem', 'd', 'r'],
108+
async run({ message, arg, $T }) {
109+
if (!arg) throw new ChatError($T('INVALID_ARGUMENTS'));
110+
const targetUser = arg.trim();
111+
112+
await deleteJoinphrase(targetUser, message.target.id);
113+
message.reply('Joinphrase deleted.' as ToTranslate);
114+
},
115+
},
116+
list: {
117+
name: 'list',
118+
help: 'Lists all joinphrases for a given user',
119+
syntax: 'CMD [user]',
120+
aliases: ['ls', 'l'],
121+
async run({ message, arg }) {
122+
const targetRoom = await getRoom(message, arg);
123+
const joinphrases = await fetchAllJoinphrases(targetRoom);
124+
125+
message.replyHTML(
126+
<table>
127+
<tbody>
128+
{joinphrases.map(joinphrase => (
129+
<tr key={joinphrase.id}>
130+
<td>
131+
<Username name={joinphrase.username} clickable />
132+
</td>
133+
<td>{joinphrase.phrase}</td>
134+
</tr>
135+
))}
136+
</tbody>
137+
</table>
138+
);
139+
},
140+
},
141+
edit: {
142+
name: 'edit',
143+
help: "Edits a user's joinphrase",
144+
syntax: 'CMD [user], [joinphrase]',
145+
flags: { allowPMs: false },
146+
aliases: ['e', 'update'],
147+
async run({ message, arg, $T }) {
148+
if (!arg) throw new ChatError($T('INVALID_ARGUMENTS'));
149+
const [username, phrase] = arg.lazySplit(/\s*,\s*/, 1).map(s => s.trim());
150+
if (!phrase) throw new ChatError($T('INVALID_ARGUMENTS'));
151+
const targetUser = username.trim();
152+
if (!(await getJoinphrase(targetUser, message.target.id))) {
153+
throw new ChatError(`${targetUser} does not have a joinphrase in ${message.target.title}...` as ToTranslate);
154+
}
155+
validateJoinphrase(phrase);
156+
await setJoinphrase(targetUser, message.target.id, phrase, message.author.name);
157+
message.reply('Joinphrase edited.' as ToTranslate);
158+
},
159+
},
160+
},
161+
async run({ run, arg }) {
162+
if (arg) await run(`joinphrases view ${arg}`);
163+
else await run(`help joinphrases`);
164+
},
165+
};

0 commit comments

Comments
 (0)