2026-05-16 00:06:13 +07:00
|
|
|
import type { Client } from "discord.js";
|
|
|
|
|
import type { AppConfig } from "../config";
|
2026-05-21 15:11:32 +07:00
|
|
|
import { createDb } from "../db";
|
2026-05-16 00:06:13 +07:00
|
|
|
import { BoosterRoleService } from "../services/boosterRoleService";
|
2026-05-21 15:11:32 +07:00
|
|
|
import { DrizzleBoosterRoleStore } from "../services/drizzleBoosterRoleStore";
|
2026-05-16 00:06:13 +07:00
|
|
|
import { DiscordRoleRepository } from "../services/discordRoleRepository";
|
|
|
|
|
import { handleGuildMemberUpdate } from "./events/guildMemberUpdate";
|
|
|
|
|
import { handleInteraction } from "./interactionHandler";
|
|
|
|
|
|
|
|
|
|
export function attachBotHandlers(client: Client, config: AppConfig): void {
|
2026-05-21 15:11:32 +07:00
|
|
|
const db = createDb(config.databaseUrl);
|
|
|
|
|
const store = new DrizzleBoosterRoleStore(db);
|
2026-05-16 00:06:13 +07:00
|
|
|
|
|
|
|
|
client.on("interactionCreate", async (interaction) => {
|
|
|
|
|
if (!interaction.isChatInputCommand() || !interaction.guild) return;
|
|
|
|
|
|
|
|
|
|
const service = new BoosterRoleService(
|
|
|
|
|
store,
|
2026-06-06 03:06:00 +07:00
|
|
|
new DiscordRoleRepository(interaction.guild),
|
2026-05-16 00:06:13 +07:00
|
|
|
{ anchorPosition: resolveAnchorPosition(interaction.guild, config.boosterRoleAnchorRoleId) }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await handleInteraction(interaction, service, {
|
2026-05-21 15:27:10 +07:00
|
|
|
isBoosting: async () => memberHasRole(interaction.member, config.boosterEligibilityRoleId)
|
2026-05-16 00:06:13 +07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client.on("guildMemberUpdate", async (oldMember, newMember) => {
|
|
|
|
|
const service = new BoosterRoleService(
|
|
|
|
|
store,
|
2026-06-06 03:06:00 +07:00
|
|
|
new DiscordRoleRepository(newMember.guild),
|
2026-05-16 00:06:13 +07:00
|
|
|
{ anchorPosition: resolveAnchorPosition(newMember.guild, config.boosterRoleAnchorRoleId) }
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-21 15:27:10 +07:00
|
|
|
await handleGuildMemberUpdate(oldMember, newMember, service, config.boosterEligibilityRoleId);
|
2026-05-16 00:06:13 +07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 15:27:10 +07:00
|
|
|
function memberHasRole(member: unknown, roleId: string): boolean {
|
|
|
|
|
return Boolean(
|
|
|
|
|
member &&
|
|
|
|
|
typeof member === "object" &&
|
|
|
|
|
"roles" in member &&
|
|
|
|
|
member.roles &&
|
|
|
|
|
typeof member.roles === "object" &&
|
|
|
|
|
"cache" in member.roles &&
|
|
|
|
|
member.roles.cache &&
|
|
|
|
|
typeof member.roles.cache === "object" &&
|
|
|
|
|
"has" in member.roles.cache &&
|
|
|
|
|
typeof member.roles.cache.has === "function" &&
|
|
|
|
|
member.roles.cache.has(roleId)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-16 00:32:29 +07:00
|
|
|
function resolveAnchorPosition(guild: { members: { me: { roles: { highest: { position: number } } } | null }; roles: { cache: { get(id: string): { position: number } | undefined } } }, anchorRoleId: string | null): number {
|
2026-05-21 15:27:10 +07:00
|
|
|
const botPosition = botRolePosition(guild);
|
|
|
|
|
if (!anchorRoleId) return botPosition;
|
|
|
|
|
const anchorPosition = guild.roles.cache.get(anchorRoleId)?.position ?? botPosition;
|
|
|
|
|
return Math.min(anchorPosition, botPosition);
|
2026-05-16 00:32:29 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function botRolePosition(guild: { members: { me: { roles: { highest: { position: number } } } | null } }): number {
|
|
|
|
|
return guild.members.me?.roles.highest.position ?? 1;
|
2026-05-16 00:06:13 +07:00
|
|
|
}
|