feat: add booster eligibility role and logging functionality

- Added BOOSTER_ELIGIBILITY_ROLE_ID and LOG_LEVEL to .env.example and README.md
- Updated GitHub Actions workflow to include new environment variables
- Enhanced configuration loading to support booster eligibility role
- Implemented role checking logic in bot handlers and guild member updates
- Introduced logging using Winston for better traceability of actions
- Created tests for guild member update handling to ensure correct role removal
- Refactored DiscordRoleRepository to simplify role position resolution
This commit is contained in:
MythEclipse
2026-05-21 15:27:10 +07:00
parent bfa91063ff
commit cc6975825f
13 changed files with 162 additions and 22 deletions
@@ -0,0 +1,36 @@
import { describe, expect, test } from "bun:test";
import { handleGuildMemberUpdate } from "./guildMemberUpdate";
class FakeService {
removed: Array<{ guildId: string; userId: string }> = [];
async removeRoleForLostBoost(input: { guildId: string; userId: string }): Promise<void> {
this.removed.push(input);
}
}
function member(hasRole: boolean) {
return {
id: "user",
guild: { id: "guild" },
roles: { cache: { has: (roleId: string) => roleId === "booster-role" && hasRole } }
};
}
describe("handleGuildMemberUpdate", () => {
test("removes custom role when booster eligibility role is removed", async () => {
const service = new FakeService();
await handleGuildMemberUpdate(member(true), member(false), service, "booster-role");
expect(service.removed).toEqual([{ guildId: "guild", userId: "user" }]);
});
test("does nothing while booster eligibility role remains", async () => {
const service = new FakeService();
await handleGuildMemberUpdate(member(true), member(true), service, "booster-role");
expect(service.removed).toEqual([]);
});
});
+14 -7
View File
@@ -1,16 +1,23 @@
import type { BoosterRoleService } from "../../services/boosterRoleService";
type GuildMemberLike = {
id: string;
premiumSince: Date | null;
guild: { id: string };
roles: { cache: { has(roleId: string): boolean } };
};
export async function handleGuildMemberUpdate(oldMember: GuildMemberLike, newMember: GuildMemberLike, service: BoosterRoleService): Promise<void> {
const hadBoost = Boolean(oldMember.premiumSince);
const hasBoost = Boolean(newMember.premiumSince);
type BoosterRoleRemovalService = {
removeRoleForLostBoost(input: { guildId: string; userId: string }): Promise<void>;
};
if (hadBoost && !hasBoost) {
export async function handleGuildMemberUpdate(
oldMember: GuildMemberLike,
newMember: GuildMemberLike,
service: BoosterRoleRemovalService,
boosterEligibilityRoleId: string
): Promise<void> {
const hadBoosterRole = oldMember.roles.cache.has(boosterEligibilityRoleId);
const hasBoosterRole = newMember.roles.cache.has(boosterEligibilityRoleId);
if (hadBoosterRole && !hasBoosterRole) {
await service.removeRoleForLostBoost({ guildId: newMember.guild.id, userId: newMember.id });
}
}