Team Roles Updates
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
storage/
|
storage/
|
||||||
|
data/
|
||||||
|
|
||||||
.env
|
.env
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export default class TeamManageCommand extends BotCommand {
|
|||||||
option.setName("leader")
|
option.setName("leader")
|
||||||
.setDescription("Team leader")
|
.setDescription("Team leader")
|
||||||
.setRequired(true))
|
.setRequired(true))
|
||||||
|
.addRoleOption(option =>
|
||||||
|
option.setName("role")
|
||||||
|
.setDescription("Team role to assign to members")
|
||||||
|
.setRequired(true))
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName("description")
|
option.setName("description")
|
||||||
.setDescription("Team description")
|
.setDescription("Team description")
|
||||||
@@ -82,6 +86,7 @@ export default class TeamManageCommand extends BotCommand {
|
|||||||
if (subcommand === "create") {
|
if (subcommand === "create") {
|
||||||
const name = interaction.options.getString("name", true);
|
const name = interaction.options.getString("name", true);
|
||||||
const leader = interaction.options.getUser("leader", true);
|
const leader = interaction.options.getUser("leader", true);
|
||||||
|
const role = interaction.options.getRole("role", true);
|
||||||
const description = interaction.options.getString("description") || "";
|
const description = interaction.options.getString("description") || "";
|
||||||
|
|
||||||
const existing = await Team.findOne({ name });
|
const existing = await Team.findOne({ name });
|
||||||
@@ -92,6 +97,7 @@ export default class TeamManageCommand extends BotCommand {
|
|||||||
const team = new Team({
|
const team = new Team({
|
||||||
name,
|
name,
|
||||||
leaderId: leader.id,
|
leaderId: leader.id,
|
||||||
|
roleId: role.id,
|
||||||
description,
|
description,
|
||||||
points: 0,
|
points: 0,
|
||||||
memberCount: 0
|
memberCount: 0
|
||||||
@@ -104,6 +110,7 @@ export default class TeamManageCommand extends BotCommand {
|
|||||||
.addFields(
|
.addFields(
|
||||||
{ name: "Team Name", value: name, inline: true },
|
{ name: "Team Name", value: name, inline: true },
|
||||||
{ name: "Leader", value: `<@${leader.id}>`, inline: true },
|
{ name: "Leader", value: `<@${leader.id}>`, inline: true },
|
||||||
|
{ name: "Role", value: `<@&${role.id}>`, inline: true },
|
||||||
{ name: "Description", value: description || "None", inline: false }
|
{ name: "Description", value: description || "None", inline: false }
|
||||||
)
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
@@ -118,10 +125,26 @@ export default class TeamManageCommand extends BotCommand {
|
|||||||
return interaction.reply({ content: `❌ Team **${name}** not found.`, flags: MessageFlags.Ephemeral });
|
return interaction.reply({ content: `❌ Team **${name}** not found.`, flags: MessageFlags.Ephemeral });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove role from all team members
|
||||||
|
const teamMembers = await User.find({ teamId: team._id });
|
||||||
|
const guild = interaction.guild;
|
||||||
|
if (guild) {
|
||||||
|
for (const user of teamMembers) {
|
||||||
|
try {
|
||||||
|
const member = await guild.members.fetch(user.userId);
|
||||||
|
if (member && member.roles.cache.has(team.roleId)) {
|
||||||
|
await member.roles.remove(team.roleId);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to remove role from ${user.userId}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await User.updateMany({ teamId: team._id }, { $set: { teamId: null } });
|
await User.updateMany({ teamId: team._id }, { $set: { teamId: null } });
|
||||||
await Team.deleteOne({ _id: team._id });
|
await Team.deleteOne({ _id: team._id });
|
||||||
|
|
||||||
return interaction.reply({ content: `✅ Team **${name}** has been deleted.`, flags: MessageFlags.Ephemeral });
|
return interaction.reply({ content: `✅ Team **${name}** has been deleted and roles removed from members.`, flags: MessageFlags.Ephemeral });
|
||||||
|
|
||||||
} else if (subcommand === "change-leader") {
|
} else if (subcommand === "change-leader") {
|
||||||
const name = interaction.options.getString("name", true);
|
const name = interaction.options.getString("name", true);
|
||||||
@@ -147,8 +170,8 @@ export default class TeamManageCommand extends BotCommand {
|
|||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("📋 All Teams")
|
.setTitle("📋 All Teams")
|
||||||
.setColor(0x60a5fa)
|
.setColor(0x60a5fa)
|
||||||
.setDescription(teams.map((t, i) =>
|
.setDescription(teams.map((t: any, i: number) =>
|
||||||
`**${i + 1}.** ${t.name} - Leader: <@${t.leaderId}>\n` +
|
`**${i + 1}.** ${t.name} - Leader: <@${t.leaderId}> - Role: <@&${t.roleId}>\n` +
|
||||||
` Members: ${t.memberCount} | Points: ${t.points} (Adjusted: ${t.adjustedPoints})`
|
` Members: ${t.memberCount} | Points: ${t.points} (Adjusted: ${t.adjustedPoints})`
|
||||||
).join("\n\n"))
|
).join("\n\n"))
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
@@ -164,7 +187,7 @@ export default class TeamManageCommand extends BotCommand {
|
|||||||
return interaction.reply({ content: `❌ Team **${teamName}** not found.`, flags: MessageFlags.Ephemeral });
|
return interaction.reply({ content: `❌ Team **${teamName}** not found.`, flags: MessageFlags.Ephemeral });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await PointsManager.joinTeam(user.id, user.username, team._id.toString(), true);
|
const result = await PointsManager.joinTeam(user.id, user.username, team._id.toString(), true, interaction.guild || undefined);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return interaction.reply({ content: `✅ <@${user.id}> has been moved to team **${teamName}**.`, flags: MessageFlags.Ephemeral });
|
return interaction.reply({ content: `✅ <@${user.id}> has been moved to team **${teamName}**.`, flags: MessageFlags.Ephemeral });
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default class JoinTeamCommand extends BotCommand {
|
|||||||
return interaction.editReply({ content: `❌ Team **${teamName}** not found.` });
|
return interaction.editReply({ content: `❌ Team **${teamName}** not found.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await PointsManager.joinTeam(interaction.user.id, interaction.user.username, team._id.toString());
|
const result = await PointsManager.joinTeam(interaction.user.id, interaction.user.username, team._id.toString(), false, interaction.guild || undefined);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ export default async(Discord: any, client: BotClient) => {
|
|||||||
client.emit("birthdayCheck");
|
client.emit("birthdayCheck");
|
||||||
}, ms('30m'));
|
}, ms('30m'));
|
||||||
|
|
||||||
|
// Clean up old typst files daily
|
||||||
|
setInterval(() => {
|
||||||
|
client.emit("typstCleanup");
|
||||||
|
}, ms('24h'));
|
||||||
|
|
||||||
|
// Run cleanup once on startup
|
||||||
|
client.emit("typstCleanup");
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
20
src/events/bot/custom/typstCleanup.ts
Normal file
20
src/events/bot/custom/typstCleanup.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import BotClient from "../../../libs/BotClient";
|
||||||
|
|
||||||
|
export default async(Discord: any, client: BotClient) => {
|
||||||
|
console.log("[TypstCleanup] Running cleanup for old typst files...");
|
||||||
|
|
||||||
|
// Delete files older than 1 day (after the question day passes)
|
||||||
|
const result = await client.typst.cleanupOldFiles(1);
|
||||||
|
|
||||||
|
if (result.deleted > 0) {
|
||||||
|
console.log(`[TypstCleanup] ✅ Successfully cleaned up ${result.deleted} old typst file(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.errors > 0) {
|
||||||
|
console.error(`[TypstCleanup] ⚠️ Encountered ${result.errors} error(s) during cleanup`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.deleted === 0 && result.errors === 0) {
|
||||||
|
console.log("[TypstCleanup] No old files to clean up");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import User, { IUser } from "../models/User";
|
import User, { IUser } from "../models/User";
|
||||||
import Team, { ITeam } from "../models/Team";
|
import Team, { ITeam } from "../models/Team";
|
||||||
import Database from "./Database";
|
import Database from "./Database";
|
||||||
|
import { Guild } from "discord.js";
|
||||||
|
|
||||||
export default class PointsManager {
|
export default class PointsManager {
|
||||||
private static DAILY_POINTS = 2;
|
private static DAILY_POINTS = 2;
|
||||||
@@ -101,7 +102,7 @@ export default class PointsManager {
|
|||||||
return dayOfMonth <= 7;
|
return dayOfMonth <= 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async joinTeam(userId: string, username: string, teamId: string, requireTimeCheck: boolean = false): Promise<{ success: boolean; message: string }> {
|
public static async joinTeam(userId: string, username: string, teamId: string, requireTimeCheck: boolean = false, guild?: Guild): Promise<{ success: boolean; message: string }> {
|
||||||
const db = Database.getInstance();
|
const db = Database.getInstance();
|
||||||
if (!db.isConnected()) return { success: false, message: "Database not connected" };
|
if (!db.isConnected()) return { success: false, message: "Database not connected" };
|
||||||
|
|
||||||
@@ -125,6 +126,32 @@ export default class PointsManager {
|
|||||||
return { success: false, message: "You're already in this team" };
|
return { success: false, message: "You're already in this team" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Discord role changes if guild is provided
|
||||||
|
if (guild) {
|
||||||
|
try {
|
||||||
|
const member = await guild.members.fetch(userId);
|
||||||
|
|
||||||
|
// Remove old team role if user was in a team
|
||||||
|
if (oldTeamId) {
|
||||||
|
const oldTeam = await Team.findById(oldTeamId);
|
||||||
|
if (oldTeam && member.roles.cache.has(oldTeam.roleId)) {
|
||||||
|
await member.roles.remove(oldTeam.roleId);
|
||||||
|
console.log(`[PointsManager] Removed role ${oldTeam.roleId} from ${username}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new team role
|
||||||
|
if (!member.roles.cache.has(team.roleId)) {
|
||||||
|
await member.roles.add(team.roleId);
|
||||||
|
console.log(`[PointsManager] Added role ${team.roleId} to ${username}`);
|
||||||
|
}
|
||||||
|
} catch (roleError) {
|
||||||
|
console.error(`[PointsManager] Error managing roles for ${username}:`, roleError);
|
||||||
|
// Continue with team assignment even if role fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update old team member count
|
||||||
if (oldTeamId) {
|
if (oldTeamId) {
|
||||||
const oldTeam = await Team.findById(oldTeamId);
|
const oldTeam = await Team.findById(oldTeamId);
|
||||||
if (oldTeam) {
|
if (oldTeam) {
|
||||||
|
|||||||
@@ -112,4 +112,48 @@ ${typstCode}
|
|||||||
return result.files[0];
|
return result.files[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cleanupOldFiles(daysOld: number = 1): Promise<{ deleted: number; errors: number }> {
|
||||||
|
const storageDir = path.resolve('./storage/typst');
|
||||||
|
let deleted = 0;
|
||||||
|
let errors = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.promises.access(storageDir);
|
||||||
|
} catch {
|
||||||
|
console.log('[Typst] Storage directory does not exist, nothing to clean');
|
||||||
|
return { deleted: 0, errors: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const files = await fs.promises.readdir(storageDir);
|
||||||
|
const now = Date.now();
|
||||||
|
const threshold = daysOld * 24 * 60 * 60 * 1000; // Convert days to milliseconds
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(storageDir, file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = await fs.promises.stat(filePath);
|
||||||
|
const fileAge = now - stats.mtimeMs;
|
||||||
|
|
||||||
|
if (fileAge > threshold) {
|
||||||
|
await fs.promises.unlink(filePath);
|
||||||
|
deleted++;
|
||||||
|
console.log(`[Typst] Deleted old file: ${file}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[Typst] Error processing file ${file}:`, err);
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Typst] Cleanup complete: ${deleted} files deleted, ${errors} errors`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Typst] Error during cleanup:', err);
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { deleted, errors };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ export interface ITeam extends Document {
|
|||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
leaderId: string;
|
leaderId: string;
|
||||||
|
roleId: string;
|
||||||
points: number;
|
points: number;
|
||||||
memberCount: number;
|
memberCount: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@@ -14,6 +15,7 @@ const TeamSchema = new Schema<ITeam>({
|
|||||||
name: { type: String, required: true, unique: true, index: true },
|
name: { type: String, required: true, unique: true, index: true },
|
||||||
description: { type: String, default: "" },
|
description: { type: String, default: "" },
|
||||||
leaderId: { type: String, required: true },
|
leaderId: { type: String, required: true },
|
||||||
|
roleId: { type: String, required: true },
|
||||||
points: { type: Number, default: 0 },
|
points: { type: Number, default: 0 },
|
||||||
memberCount: { type: Number, default: 0 },
|
memberCount: { type: Number, default: 0 },
|
||||||
createdAt: { type: Date, default: Date.now },
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
|||||||
Reference in New Issue
Block a user