Skip to content

Commit 79e832b

Browse files
committed
feat: More points commands!
1 parent b8dca24 commit 79e832b

4 files changed

Lines changed: 114 additions & 26 deletions

File tree

src/database/points.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import mongoose from 'mongoose';
33
import { IS_ENABLED } from '@/enabled';
44
import { toId } from '@/tools';
55

6-
type Model = { id: string; userId: string; name: string; roomId: string; points: Record<string, number> };
6+
export type Model = { id: string; userId: string; name: string; roomId: string; points: Record<string, number> };
77
type MapModel = Omit<Model, 'points'> & { points: Map<string, number> };
88

99
const schema = new mongoose.Schema<MapModel>({
@@ -86,7 +86,7 @@ export async function queryPoints(roomId: string, order: string[], cap = DEFAULT
8686
.lean();
8787
}
8888

89-
export async function getRank(user: string, roomId: string, order: string[]): Promise<number | null | undefined> {
89+
export async function getRank(user: string, roomId: string, order: string[]): Promise<(Model & { rank: number }) | null | undefined> {
9090
if (!IS_ENABLED.DB) return;
9191
const currentPoints = await getPoints(user, roomId);
9292
if (!currentPoints) return null;
@@ -96,5 +96,5 @@ export async function getRank(user: string, roomId: string, order: string[]): Pr
9696
.sort(order.map(pointType => [`points.${pointType}`, 'desc'] as [string, 'desc']))
9797
.gt(`points.${order[0]}`, currentPoints.points[order[0]])
9898
.lean();
99-
return behindUsers.length + 1;
99+
return { ...currentPoints, rank: behindUsers.length + 1 };
100100
}

src/i18n/english.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ export default {
119119

120120
POINTS: {
121121
ROOM_NO_POINTS: '{{room}} does not have points enabled.',
122+
USER_NO_POINTS: '[[]]{{user}} has no points in the room.',
123+
USER_POINTS: '[[]]{{user}} has {{pointsList}} in {{roomName}}.',
124+
USER_POINTS_RANKED: '[[]]{{user}} is ranked #{{rank}} with {{pointsList}} in {{roomName}}.',
122125
},
123126

124127
QUOTES: {

src/ps/commands/points.tsx

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,51 @@
11
import { PSRoomConfigs } from '@/cache';
2-
import { bulkAddPoints } from '@/database/points';
2+
import { type Model as PointsModel, bulkAddPoints, getPoints, getRank, queryPoints } from '@/database/points';
33
import { IS_ENABLED } from '@/enabled';
44
import { toId } from '@/tools';
55
import { ChatError } from '@/utils/chatError';
66
import { pluralize } from '@/utils/pluralize';
77

88
import type { ToTranslate, TranslatedText } from '@/i18n/types';
99
import type { PSCommand } from '@/types/chat';
10+
import type { PSPointsType, PSRoomConfig } from '@/types/ps';
1011

1112
const NUM_PATTERN = /^-?\d+$/;
1213

14+
function getPointsType(input: string, roomPoints: NonNullable<PSRoomConfig['points']>): PSPointsType | null {
15+
const pointsId = toId(input);
16+
return (
17+
Object.values(roomPoints.types).find(
18+
({ id, aliases, singular, plural, symbol }) =>
19+
id === pointsId || aliases?.includes(pointsId) || toId(singular) === pointsId || toId(plural) === pointsId || symbol === input
20+
) ?? null
21+
);
22+
}
23+
1324
export const command: PSCommand[] = [
1425
{
1526
name: 'addpoints',
1627
help: 'Adds points to a user!',
1728
syntax: 'CMD [points], [...users]',
1829
flags: { roomOnly: true },
1930
perms: Symbol.for('points.manage'),
20-
aliases: ['addp', 'add'],
31+
aliases: ['addp', 'add', 'removep', 'remove', 'removepoints'],
2132
async run(ctx) {
2233
const { message, arg, $T, originalCommand, broadcast } = ctx;
2334
if (!IS_ENABLED.DB) throw new ChatError($T('DISABLED.DB'));
2435
const roomConfig = PSRoomConfigs[message.target.id];
2536
if (!roomConfig.points) throw new ChatError($T('COMMANDS.POINTS.ROOM_NO_POINTS', { room: message.target.title }));
2637

2738
const args = arg.split(',').map(term => term.trim());
28-
const pointsTypeInput = originalCommand.join('.') === 'add' ? args.shift() : roomConfig.points.priority[0];
39+
// If command is not explicitly 'add' or 'remove', use the first declared points type
40+
const pointsTypeInput = ['add', 'remove'].includes(originalCommand.join('.')) ? args.shift() : roomConfig.points.priority[0];
2941
if (!pointsTypeInput) throw new ChatError('Specify a points type!' as ToTranslate);
30-
const pointsId = toId(pointsTypeInput);
31-
const pointsType = Object.values(roomConfig.points.types).find(
32-
({ id, aliases, singular, plural, symbol }) =>
33-
id === pointsId ||
34-
aliases?.includes(pointsId) ||
35-
toId(singular) === pointsId ||
36-
toId(plural) === pointsId ||
37-
symbol === pointsTypeInput
38-
);
42+
43+
const pointsType = getPointsType(pointsTypeInput, roomConfig.points);
3944
if (!pointsType) throw new ChatError(`Couldn't find a points type matching ${pointsTypeInput}.` as ToTranslate);
4045

4146
const numVals = args.filter(arg => NUM_PATTERN.test(arg));
4247
if (numVals.length !== 1) throw new ChatError(`How many points? ${numVals.join('/')}` as ToTranslate);
43-
const pointsAmount = parseInt(numVals[0]);
48+
const pointsAmount = (originalCommand.join('.').includes('remove') ? -1 : 1) * parseInt(numVals[0]);
4449
if (Math.abs(pointsAmount) > 1e6) throw new ChatError($T('SCREW_YOU'));
4550

4651
const users = args.filter(arg => !NUM_PATTERN.test(arg));
@@ -59,4 +64,85 @@ export const command: PSCommand[] = [
5964
);
6065
},
6166
},
67+
{
68+
name: 'atm',
69+
help: "Displays a user's current points.",
70+
syntax: 'CMD [user?]',
71+
aliases: ['points'],
72+
async run({ message, $T, args, broadcast }) {
73+
if (!IS_ENABLED.DB) throw new ChatError($T('DISABLED.DB'));
74+
const room = message.parent.getRoom(message.type === 'chat' ? message.target.id : (args.shift() ?? ''));
75+
if (!room) throw new ChatError($T('INVALID_ROOM_ID'));
76+
const roomConfig = PSRoomConfigs[room.id];
77+
if (!roomConfig?.points) throw new ChatError($T('COMMANDS.POINTS.ROOM_NO_POINTS'));
78+
const target = args.join(' ').trim() || message.author.id;
79+
const targetPoints = await getPoints(target, room.id);
80+
if (!targetPoints) throw new ChatError($T('COMMANDS.POINTS.USER_NO_POINTS'));
81+
const roomPoints = roomConfig.points;
82+
return broadcast(
83+
$T('COMMANDS.POINTS.USER_POINTS', {
84+
user: targetPoints.name,
85+
roomName: room.title,
86+
pointsList: roomPoints.priority
87+
.map(type => pluralize<TranslatedText>(targetPoints.points[type], roomPoints.types[type]))
88+
.list($T),
89+
})
90+
);
91+
},
92+
},
93+
{
94+
name: 'rank',
95+
help: "Displays a user's rank on the leaderboard.",
96+
syntax: 'CMD [user?]',
97+
async run({ message, $T, args, broadcast }) {
98+
if (!IS_ENABLED.DB) throw new ChatError($T('DISABLED.DB'));
99+
const room = message.parent.getRoom(message.type === 'chat' ? message.target.id : (args.shift() ?? ''));
100+
if (!room) throw new ChatError($T('INVALID_ROOM_ID'));
101+
const roomConfig = PSRoomConfigs[room.id];
102+
if (!roomConfig?.points) throw new ChatError($T('COMMANDS.POINTS.ROOM_NO_POINTS'));
103+
const roomPoints = roomConfig.points;
104+
const target = args.join(' ').trim() || message.author.id;
105+
const targetPoints = await getRank(target, room.id, roomPoints.priority);
106+
if (!targetPoints) throw new ChatError($T('COMMANDS.POINTS.USER_NO_POINTS'));
107+
return broadcast(
108+
$T('COMMANDS.POINTS.USER_POINTS_RANKED', {
109+
user: targetPoints.name,
110+
rank: targetPoints.rank,
111+
roomName: room.title,
112+
pointsList: roomPoints.priority
113+
.map(type => pluralize<TranslatedText>(targetPoints.points[type], roomPoints.types[type]))
114+
.list($T),
115+
})
116+
);
117+
},
118+
},
119+
{
120+
name: 'leaderboard',
121+
help: 'Shows the leaderboard!',
122+
syntax: 'CMD [cap/priority]',
123+
aliases: ['lb'],
124+
async run({ message, $T, args, broadcastHTML }) {
125+
if (!IS_ENABLED.DB) throw new ChatError($T('DISABLED.DB'));
126+
// TODO: Maybe have some helper function to parse room name if not given
127+
const room = message.parent.getRoom(message.type === 'chat' ? message.target.id : (args.shift() ?? ''));
128+
if (!room) throw new ChatError($T('INVALID_ROOM_ID'));
129+
const roomConfig = PSRoomConfigs[room.id];
130+
if (!roomConfig?.points) throw new ChatError($T('COMMANDS.POINTS.ROOM_NO_POINTS'));
131+
const roomPoints = roomConfig.points;
132+
133+
let queryData: PointsModel[] | undefined;
134+
const arg = args.join('').trim();
135+
if (NUM_PATTERN.test(arg)) queryData = await queryPoints(room.id, roomPoints.priority, +arg);
136+
else if (arg) {
137+
const sortBy = getPointsType(arg, roomPoints)?.id;
138+
if (!sortBy) throw new ChatError($T('INVALID_ARGUMENTS'));
139+
queryData = await queryPoints(room.id, [sortBy, ...roomPoints.priority.filter(type => type !== sortBy)]);
140+
} else queryData = await queryPoints(room.id, roomPoints.priority);
141+
142+
if (!queryData) throw new Error(`Somehow I didn't manage to get any data! Send help please (${room.id}, ${args})`);
143+
144+
// TODO: Render board
145+
broadcastHTML(<>{JSON.stringify(queryData, null, 2)}</>);
146+
},
147+
},
62148
];

src/types/ps.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ export type PSMessage = Message;
55

66
export type AuthKey = Perms & string;
77

8+
export type PSPointsType = {
9+
id: string;
10+
singular: string;
11+
plural: string;
12+
symbol: string;
13+
aliases?: string[];
14+
};
15+
816
export type PSRoomConfig = {
917
roomId: string;
1018
roomName?: string;
@@ -22,16 +30,7 @@ export type PSRoomConfig = {
2230
[key: string]: Perms;
2331
} | null;
2432
points?: {
25-
types: Record<
26-
string,
27-
{
28-
id: string;
29-
singular: string;
30-
plural: string;
31-
symbol: string;
32-
aliases?: string[];
33-
}
34-
>;
33+
types: Record<string, PSPointsType>;
3534
priority: string[];
3635
format: string;
3736
} | null;

0 commit comments

Comments
 (0)