implement db into code where needed instead.

This commit is contained in:
2026-09-13 17:33:48 +08:00
parent b3521938f5
commit 141f68ba5e
9 changed files with 206 additions and 96 deletions
+82
View File
@@ -0,0 +1,82 @@
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime";
import database from "../../database";
// ----------
@SlashCommand(
new CommandBuilder('points', "Adjust a user's points.")
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
.setContexts(Constants.InteractionContextType.Guild)
.setCommandType(Constants.ApplicationCommandType.ChatInput)
.setMemberPermission(Constants.PermissionFlagsBits.ManageGuild)
.addSubcommand('give', 'Give points to a user.', builder => {
builder.addUserOption('target', 'The user to modify.', true);
builder.addNumberOption({ name: 'points', description: 'The points to give.', required: true, min_value: 1 })
})
.addSubcommand('take', 'Take points from a user.', builder => {
builder.addUserOption('target', 'The user to modify.', true);
builder.addNumberOption({ name: 'points', description: 'The points to take.', required: true, min_value: 1 })
})
.addSubcommand('set', 'Set the points of a user.', builder => {
builder.addUserOption('target', 'The user to modify.', true);
builder.addNumberOption({ name: 'points', description: 'The new points value.', required: true })
})
)
class RankCommand extends Command<CommandClient> {
users = [
'1363132678022631428', // Nix.
'454653142500507649', // Ann.
'147433323503943680' // Pond.
];
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
if (!interaction.inGuild()) return;
const target = interaction.getRequiredUser('target');
const points = interaction.getRequiredNumber('points');
switch (interaction.subcommand) {
case 'give':
await database`
INSERT INTO points AS p (user_id, guild_id, points)
VALUES (${target.id}, ${interaction.guild.id}, ${points})
ON CONFLICT (user_id, guild_id)
DO UPDATE SET points = p.points + ${points}
`;
return await interaction.createMessage({
content: `Done! *${target.username}* has been given **${points}** points.`,
flags: Constants.MessageFlags.Ephemeral
});
case 'take':
await database`
INSERT INTO points AS p (user_id, guild_id, points)
VALUES ${target.id}, ${interaction.guild.id}, 0)
ON CONFLICT (user_id, guild_id)
DO UPDATE SET points = GREATEST(p.points - ${points}, 0)
`;
return await interaction.createMessage({
content: `Done! *${target.username}* has lost **${points}** points.`,
flags: Constants.MessageFlags.Ephemeral
});
case 'set':
await database`
INSERT INTO points (user_id, guild_id, points)
VALUES (${target.id}, ${interaction.guild.id}, ${points})
ON CONFLICT (user_id, guild_id)
DO UPDATE SET points = ${points}
`;
return await interaction.createMessage({
content: `Done! *${target.username}*'s points has been set to **${points}**.`,
flags: Constants.MessageFlags.Ephemeral
});
}
}
}
export default new RankCommand('rank');
+44
View File
@@ -0,0 +1,44 @@
import { Command, CommandBuilder, CommandClient, CommandInteraction, Constants, SlashCommand } from "athena-prime";
import database from "../../database";
// ----------
@SlashCommand(
new CommandBuilder('wipe', 'Wipes all points for the guild or specific user. THIS DOES NOT ASK FOR CONFIRMATION!')
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
.setContexts(Constants.InteractionContextType.Guild)
.setCommandType(Constants.ApplicationCommandType.ChatInput)
.setMemberPermission(Constants.PermissionFlagsBits.ManageGuild)
.addUserOption('target', "Optional. The user to wipe.", false)
)
class RankCommand extends Command<CommandClient> {
users = [
'1363132678022631428', // Nix.
'454653142500507649', // Ann.
'147433323503943680' // Pond.
];
async handleCommand(context: CommandClient<any, any>, interaction: CommandInteraction) {
if (!interaction.inGuild()) return;
const target = interaction.getUser('target');
if (!target) {
await database` UPDATE points SET points = 0 WHERE guild_id = ${interaction.guild.id}`;
return await interaction.createMessage({
content: 'Done! All points for this guild have been cleared.',
flags: Constants.MessageFlags.Ephemeral
});
}
await database` UPDATE points SET points = 0 WHERE user_id = ${target.id} AND guild_id = ${interaction.guild.id} `;
return await interaction.createMessage({
content: `Done! *${target.username}*'s points have been wiped.`,
flags: Constants.MessageFlags.Ephemeral
});
}
}
export default new RankCommand('rank');
+3 -1
View File
@@ -1,6 +1,8 @@
import points from './admin/points';
import wipe from './admin/wipe';
import leaderboard from './leaderboard'; import leaderboard from './leaderboard';
import rank from './rank'; import rank from './rank';
// ---------- // ----------
export default [leaderboard, rank]; export default [points, wipe, leaderboard, rank];
+16 -1
View File
@@ -3,6 +3,19 @@ import database from '../database';
// ---------- // ----------
async function getLeaderboard(guild_id: string, limit: number = 10): Promise<{ user_id: string; points: number }[]> {
return await database`
SELECT user_id, points
FROM points
WHERE guild_id = ${guild_id}
AND points > 0
ORDER BY points DESC, user_id ASC
LIMIT ${limit}
`;
}
// ----------
@SlashCommand( @SlashCommand(
new CommandBuilder('leaderboard', 'Get the petting leaderboard.') new CommandBuilder('leaderboard', 'Get the petting leaderboard.')
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall) .setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
@@ -14,7 +27,7 @@ class LeaderboardCommand extends Command<CommandClient> {
if (!interaction.inGuild()) return; if (!interaction.inGuild()) return;
await interaction.defer(); await interaction.defer();
const data = await database.leaderboard.getLeaderboard(interaction.guild.id); const data = await getLeaderboard(interaction.guild.id);
const description = data.length const description = data.length
? data ? data
.map((entry, index) => `**${index + 1}.** <@${entry.user_id}> — **${entry.points}** points!`) .map((entry, index) => `**${index + 1}.** <@${entry.user_id}> — **${entry.points}** points!`)
@@ -39,4 +52,6 @@ class LeaderboardCommand extends Command<CommandClient> {
} }
} }
// ----------
export default new LeaderboardCommand('leaderboard'); export default new LeaderboardCommand('leaderboard');
+34 -2
View File
@@ -3,6 +3,36 @@ import database from "../database";
// ---------- // ----------
async function getPoints(id: string, guild_id: string): Promise<number> {
const [row] = await database`
SELECT points
FROM points
WHERE user_id = ${id}
AND guild_id = ${guild_id}
`;
return row?.points ?? 0;
}
async function getLeaderboardPosition(id: string, guild_id: string): Promise<number | null> {
const [row] = await database`
SELECT position
FROM (
SELECT
user_id,
RANK() OVER (ORDER BY points DESC) AS position
FROM points
WHERE guild_id = ${guild_id}
AND points > 0
) leaderboard
WHERE user_id = ${id}
`;
return row?.position ?? null;
}
// ----------
@SlashCommand( @SlashCommand(
new CommandBuilder('rank', 'See your rank information.') new CommandBuilder('rank', 'See your rank information.')
.setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall) .setIntegrationTypes(Constants.ApplicationIntegrationType.GuildInstall)
@@ -14,8 +44,8 @@ class RankCommand extends Command<CommandClient> {
if (!interaction.inGuild()) return; if (!interaction.inGuild()) return;
await interaction.defer(); await interaction.defer();
const _points = await database.points.getPoints(interaction.member.id, interaction.guild.id); const _points = await getPoints(interaction.member.id, interaction.guild.id);
const _rank = await database.leaderboard.getLeaderboardPosition(interaction.member.id, interaction.guild.id); const _rank = await getLeaderboardPosition(interaction.member.id, interaction.guild.id);
await interaction.createMessage({ await interaction.createMessage({
embeds: [{ embeds: [{
@@ -36,4 +66,6 @@ class RankCommand extends Command<CommandClient> {
} }
} }
// ----------
export default new RankCommand('rank'); export default new RankCommand('rank');
+1 -88
View File
@@ -2,91 +2,4 @@ import postgres from "postgres";
// ---------- // ----------
const client = postgres(process.env.DATABASE_URL); export default postgres(process.env.DATABASE_URL);
async function init() {
await client`
CREATE TABLE IF NOT EXISTS points (
user_id TEXT NOT NULL,
guild_id TEXT NOT NULL,
points INTEGER NOT NULL DEFAULT 0 CHECK (points >= 0),
PRIMARY KEY (user_id, guild_id)
)
`;
}
async function getPoints(id: string, guild_id: string): Promise<number> {
const [row] = await client`
SELECT points
FROM points
WHERE user_id = ${id}
AND guild_id = ${guild_id}
`;
return row?.points ?? 0;
}
async function addPoints(id: string, guild_id: string, count: number) {
await client`
INSERT INTO points AS p (user_id, guild_id, points)
VALUES (${id}, ${guild_id}, ${count})
ON CONFLICT (user_id, guild_id)
DO UPDATE SET points = p.points + ${count}
`;
}
async function takePoints(id: string, guild_id: string, count: number) {
await client`
INSERT INTO points AS p (user_id, guild_id, points)
VALUES (${id}, ${guild_id}, 0)
ON CONFLICT (user_id, guild_id)
DO UPDATE SET points = GREATEST(p.points - ${count}, 0)
`;
}
// ----------
async function getLeaderboard(guild_id: string, limit: number = 10): Promise<{ user_id: string; points: number }[]> {
return await client`
SELECT user_id, points
FROM points
WHERE guild_id = ${guild_id}
AND points > 0
ORDER BY points DESC, user_id ASC
LIMIT ${limit}
`;
}
async function getLeaderboardPosition(id: string, guild_id: string): Promise<number | null> {
const [row] = await client`
SELECT position
FROM (
SELECT
user_id,
RANK() OVER (ORDER BY points DESC) AS position
FROM points
WHERE guild_id = ${guild_id}
AND points > 0
) leaderboard
WHERE user_id = ${id}
`;
return row?.position ?? null;
}
// ----------
export default {
points: {
init,
getPoints,
addPoints,
takePoints
},
leaderboard: {
getLeaderboard,
getLeaderboardPosition
}
};
+7 -1
View File
@@ -8,7 +8,13 @@ class MessageReactionAddEvent extends Event<CommandClient> {
async handle(context: CommandClient<any, any>, message: Message, emoji: Constants.APIEmoji, member: Member) { async handle(context: CommandClient<any, any>, message: Message, emoji: Constants.APIEmoji, member: Member) {
if (!message.guildID || emoji.id != '1547870117080342548' || member.id == message.author.id) return; if (!message.guildID || emoji.id != '1547870117080342548' || member.id == message.author.id) return;
await database.points.addPoints(message.author.id, member.guild.id, 1);
await database`
INSERT INTO points AS p (user_id, guild_id, points)
VALUES (${message.author.id}, ${member.guild.id}, 1)
ON CONFLICT (user_id, guild_id)
DO UPDATE SET points = p.points + 1
`;
} }
} }
+7 -2
View File
@@ -8,9 +8,14 @@ class MessageReactionRemoveEvent extends Event<CommandClient> {
async handle(context: CommandClient<any, any>, message: { id: string; channel: { id: string } }, emoji: Constants.APIEmoji, userId: string) { async handle(context: CommandClient<any, any>, message: { id: string; channel: { id: string } }, emoji: Constants.APIEmoji, userId: string) {
const _message = await context.getMessage(message.channel.id, message.id); const _message = await context.getMessage(message.channel.id, message.id);
if (!_message.guildID || emoji.id != '1547870117080342548' || userId == _message.author.id) return; if (!_message.guildID || emoji.id != '1547870117080342548' || userId == _message.author.id) return;
await database.points.takePoints(_message.author.id, _message.guildID, 1);
await database`
INSERT INTO points AS p (user_id, guild_id, points)
VALUES ${_message.author.id}, ${_message.guildID}, 0)
ON CONFLICT (user_id, guild_id)
DO UPDATE SET points = GREATEST(p.points - 1, 0)
`;
} }
} }
+12 -1
View File
@@ -26,5 +26,16 @@ events.forEach((event) => client.registerEvent(event, true));
// ---------- // ----------
database.points.init(); database`
CREATE TABLE IF NOT EXISTS points (
user_id TEXT NOT NULL,
guild_id TEXT NOT NULL,
points INTEGER NOT NULL DEFAULT 0 CHECK (points >= 0),
PRIMARY KEY (user_id, guild_id)
)
`;
// ----------
client.connect(); client.connect();