refactor: extract Discord.js code from shared services into bot layer
Move terminal.service.ts and prune.service.ts entirely to bot/modules/ since they are Discord-specific. Split lootdrop.service.ts: pure logic (activity tracking, DB ops, claim) stays in shared/, Discord operations (message sending, channel interactions) move to bot/modules/economy/ lootdrop.handler.ts. Move effect registry/handlers/types from bot/ to shared/modules/inventory/ since they contain no Discord.js imports and are needed by inventory.service.ts in shared. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,7 +73,7 @@ async function handler(ctx: RouteContext): Promise<Response | null> {
|
|||||||
*/
|
*/
|
||||||
if (pathname === "/api/lootdrops" && method === "POST") {
|
if (pathname === "/api/lootdrops" && method === "POST") {
|
||||||
return withErrorHandling(async () => {
|
return withErrorHandling(async () => {
|
||||||
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
|
const { spawnLootdrop } = await import("../../../bot/modules/economy/lootdrop.handler");
|
||||||
const { AuroraClient } = await import("../../../bot/lib/BotClient");
|
const { AuroraClient } = await import("../../../bot/lib/BotClient");
|
||||||
const { TextChannel } = await import("discord.js");
|
const { TextChannel } = await import("discord.js");
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ async function handler(ctx: RouteContext): Promise<Response | null> {
|
|||||||
return errorResponse("Invalid channel. Must be a TextChannel.", 400);
|
return errorResponse("Invalid channel. Must be a TextChannel.", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
await lootdropService.spawnLootdrop(channel, data.amount, data.currency);
|
await spawnLootdrop(channel, data.amount, data.currency);
|
||||||
|
|
||||||
return jsonResponse({ success: true }, 201);
|
return jsonResponse({ success: true }, 201);
|
||||||
}, "spawn lootdrop");
|
}, "spawn lootdrop");
|
||||||
@@ -110,8 +110,8 @@ async function handler(ctx: RouteContext): Promise<Response | null> {
|
|||||||
if (!messageId) return null;
|
if (!messageId) return null;
|
||||||
|
|
||||||
return withErrorHandling(async () => {
|
return withErrorHandling(async () => {
|
||||||
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
|
const { deleteLootdrop } = await import("../../../bot/modules/economy/lootdrop.handler");
|
||||||
const success = await lootdropService.deleteLootdrop(messageId);
|
const success = await deleteLootdrop(messageId);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return errorResponse("Lootdrop not found", 404);
|
return errorResponse("Lootdrop not found", 404);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createCommand } from "@shared/lib/utils";
|
import { createCommand } from "@shared/lib/utils";
|
||||||
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js";
|
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js";
|
||||||
import { config } from "@shared/lib/config";
|
import { config } from "@shared/lib/config";
|
||||||
import { pruneService } from "@shared/modules/moderation/prune.service";
|
import { pruneService } from "@modules/moderation/prune.service";
|
||||||
import {
|
import {
|
||||||
getConfirmationMessage,
|
getConfirmationMessage,
|
||||||
getProgressEmbed,
|
getProgressEmbed,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { createCommand } from "@shared/lib/utils";
|
import { createCommand } from "@shared/lib/utils";
|
||||||
import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, TextChannel } from "discord.js";
|
import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, TextChannel } from "discord.js";
|
||||||
import { terminalService } from "@shared/modules/terminal/terminal.service";
|
import { terminalService } from "@modules/system/terminal.service";
|
||||||
import { createErrorEmbed } from "@/lib/embeds";
|
import { createErrorEmbed } from "@/lib/embeds";
|
||||||
import { withCommandErrorHandling } from "@lib/commandUtils";
|
import { withCommandErrorHandling } from "@lib/commandUtils";
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const event: Event<Events.MessageCreate> = {
|
|||||||
levelingService.processChatXp(message.author.id);
|
levelingService.processChatXp(message.author.id);
|
||||||
|
|
||||||
// Activity Tracking for Lootdrops
|
// Activity Tracking for Lootdrops
|
||||||
import("@shared/modules/economy/lootdrop.service").then(m => m.lootdropService.processMessage(message));
|
import("@modules/economy/lootdrop.handler").then(m => m.processLootdropMessage(message));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
60
bot/modules/economy/lootdrop.handler.ts
Normal file
60
bot/modules/economy/lootdrop.handler.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Message, TextChannel } from "discord.js";
|
||||||
|
import { lootdropService } from "@shared/modules/economy/lootdrop.service";
|
||||||
|
import { getLootdropMessage } from "./lootdrop.view";
|
||||||
|
import { terminalService } from "@modules/system/terminal.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a Discord message for lootdrop activity tracking.
|
||||||
|
* Called from messageCreate event handler.
|
||||||
|
*/
|
||||||
|
export async function processLootdropMessage(message: Message): Promise<void> {
|
||||||
|
if (message.author.bot || !message.guild) return;
|
||||||
|
|
||||||
|
const { shouldSpawn } = lootdropService.trackActivity(message.channel.id);
|
||||||
|
|
||||||
|
if (shouldSpawn) {
|
||||||
|
await spawnLootdrop(message.channel as TextChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a lootdrop in a Discord channel.
|
||||||
|
* Used by both bot events and API routes.
|
||||||
|
*/
|
||||||
|
export async function spawnLootdrop(
|
||||||
|
channel: TextChannel,
|
||||||
|
overrideReward?: number,
|
||||||
|
overrideCurrency?: string
|
||||||
|
): Promise<void> {
|
||||||
|
const { reward, currency } = lootdropService.calculateReward(overrideReward, overrideCurrency);
|
||||||
|
const { content, files, components } = await getLootdropMessage(reward, currency);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sentMessage = await channel.send({ content, files, components });
|
||||||
|
await lootdropService.persistLootdrop(sentMessage.id, channel.id, reward, currency);
|
||||||
|
terminalService.update(channel.guildId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to spawn lootdrop:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a lootdrop from DB and Discord.
|
||||||
|
*/
|
||||||
|
export async function deleteLootdrop(messageId: string): Promise<boolean> {
|
||||||
|
const result = await lootdropService.removeLootdrop(messageId);
|
||||||
|
if (!result) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { AuroraClient } = await import("@/lib/BotClient");
|
||||||
|
const channel = await AuroraClient.channels.fetch(result.channelId) as TextChannel;
|
||||||
|
if (channel) {
|
||||||
|
const message = await channel.messages.fetch(messageId);
|
||||||
|
if (message) await message.delete();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not delete lootdrop message from Discord:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { ButtonInteraction } from "discord.js";
|
|||||||
import { lootdropService } from "@shared/modules/economy/lootdrop.service";
|
import { lootdropService } from "@shared/modules/economy/lootdrop.service";
|
||||||
import { UserError } from "@shared/lib/errors";
|
import { UserError } from "@shared/lib/errors";
|
||||||
import { getLootdropClaimedMessage } from "./lootdrop.view";
|
import { getLootdropClaimedMessage } from "./lootdrop.view";
|
||||||
|
import { terminalService } from "@modules/system/terminal.service";
|
||||||
|
|
||||||
export async function handleLootdropInteraction(interaction: ButtonInteraction) {
|
export async function handleLootdropInteraction(interaction: ButtonInteraction) {
|
||||||
if (interaction.customId === "lootdrop_claim") {
|
if (interaction.customId === "lootdrop_claim") {
|
||||||
@@ -13,6 +14,9 @@ export async function handleLootdropInteraction(interaction: ButtonInteraction)
|
|||||||
throw new UserError(result.error || "Failed to claim.");
|
throw new UserError(result.error || "Failed to claim.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update terminal display after successful claim
|
||||||
|
terminalService.update();
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
content: `🎉 You successfully claimed **${result.amount} ${result.currency}**!`
|
content: `🎉 You successfully claimed **${result.amount} ${result.currency}**!`
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { temporaryRoleService } from "@shared/modules/system/temp-role.service";
|
import { temporaryRoleService } from "@shared/modules/system/temp-role.service";
|
||||||
import { terminalService } from "@shared/modules/terminal/terminal.service";
|
import { terminalService } from "./terminal.service";
|
||||||
|
|
||||||
export const schedulerService = {
|
export const schedulerService = {
|
||||||
start: () => {
|
start: () => {
|
||||||
|
|||||||
@@ -82,92 +82,81 @@ describe("lootdropService", () => {
|
|||||||
mockModifyUserBalance.mockRestore();
|
mockModifyUserBalance.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("processMessage", () => {
|
describe("trackActivity", () => {
|
||||||
it("should track activity but not spawn if minMessages not reached", async () => {
|
it("should track activity but not spawn if minMessages not reached", () => {
|
||||||
const mockChannel = { id: "chan1", send: mock() };
|
const result1 = lootdropService.trackActivity("chan1");
|
||||||
const mockMessage = {
|
const result2 = lootdropService.trackActivity("chan1");
|
||||||
author: { bot: false },
|
|
||||||
guild: {},
|
|
||||||
channel: mockChannel
|
|
||||||
};
|
|
||||||
|
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
expect(result1.shouldSpawn).toBe(false);
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
expect(result2.shouldSpawn).toBe(false);
|
||||||
|
|
||||||
// Expect no spawn attempt
|
|
||||||
expect(mockChannel.send).not.toHaveBeenCalled();
|
|
||||||
// Internal state check if possible, or just behavior
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should spawn lootdrop if minMessages reached and chance hits", async () => {
|
it("should spawn lootdrop if minMessages reached and chance hits", () => {
|
||||||
const mockChannel = { id: "chan1", send: mock() };
|
|
||||||
const mockMessage = {
|
|
||||||
author: { bot: false },
|
|
||||||
guild: {},
|
|
||||||
channel: mockChannel
|
|
||||||
};
|
|
||||||
|
|
||||||
mockChannel.send.mockResolvedValue({ id: "msg1" });
|
|
||||||
Math.random = () => 0.01; // Force hit (0.01 < 0.5)
|
Math.random = () => 0.01; // Force hit (0.01 < 0.5)
|
||||||
|
|
||||||
// Send 3 messages
|
// Send 3 messages
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
const result = lootdropService.trackActivity("chan1");
|
||||||
|
|
||||||
expect(mockChannel.send).toHaveBeenCalled();
|
expect(result.shouldSpawn).toBe(true);
|
||||||
expect(mockInsert).toHaveBeenCalledWith(lootdrops);
|
|
||||||
|
|
||||||
// Verify DB insert
|
|
||||||
expect(mockValues).toHaveBeenCalledWith(expect.objectContaining({
|
|
||||||
channelId: "chan1",
|
|
||||||
messageId: "msg1",
|
|
||||||
currency: "GOLD"
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not spawn if chance fails", async () => {
|
it("should not spawn if chance fails", () => {
|
||||||
const mockChannel = { id: "chan1", send: mock() };
|
|
||||||
const mockMessage = {
|
|
||||||
author: { bot: false },
|
|
||||||
guild: {},
|
|
||||||
channel: mockChannel
|
|
||||||
};
|
|
||||||
|
|
||||||
Math.random = () => 0.99; // Force fail (0.99 > 0.5)
|
Math.random = () => 0.99; // Force fail (0.99 > 0.5)
|
||||||
|
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
const result = lootdropService.trackActivity("chan1");
|
||||||
|
|
||||||
expect(mockChannel.send).not.toHaveBeenCalled();
|
expect(result.shouldSpawn).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should respect cooldowns", async () => {
|
it("should respect cooldowns", () => {
|
||||||
const mockChannel = { id: "chan1", send: mock() };
|
|
||||||
const mockMessage = {
|
|
||||||
author: { bot: false },
|
|
||||||
guild: {},
|
|
||||||
channel: mockChannel
|
|
||||||
};
|
|
||||||
mockChannel.send.mockResolvedValue({ id: "msg1" });
|
|
||||||
|
|
||||||
Math.random = () => 0.01; // Force hit
|
Math.random = () => 0.01; // Force hit
|
||||||
|
|
||||||
// Trigger spawn
|
// Trigger spawn
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
const result1 = lootdropService.trackActivity("chan1");
|
||||||
|
|
||||||
expect(mockChannel.send).toHaveBeenCalledTimes(1);
|
expect(result1.shouldSpawn).toBe(true);
|
||||||
mockChannel.send.mockClear();
|
|
||||||
|
|
||||||
// Try again immediately (cooldown active)
|
// Try again immediately (cooldown active)
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
lootdropService.trackActivity("chan1");
|
||||||
await lootdropService.processMessage(mockMessage as any);
|
const result2 = lootdropService.trackActivity("chan1");
|
||||||
|
|
||||||
expect(mockChannel.send).not.toHaveBeenCalled();
|
expect(result2.shouldSpawn).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("calculateReward", () => {
|
||||||
|
it("should return override values when provided", () => {
|
||||||
|
const result = lootdropService.calculateReward(500, "SILVER");
|
||||||
|
expect(result.reward).toBe(500);
|
||||||
|
expect(result.currency).toBe("SILVER");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return random reward within range when no override", () => {
|
||||||
|
const result = lootdropService.calculateReward();
|
||||||
|
expect(result.reward).toBeGreaterThanOrEqual(10);
|
||||||
|
expect(result.reward).toBeLessThanOrEqual(100);
|
||||||
|
expect(result.currency).toBe("GOLD");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("persistLootdrop", () => {
|
||||||
|
it("should insert lootdrop into database", async () => {
|
||||||
|
await lootdropService.persistLootdrop("msg1", "chan1", 50, "GOLD");
|
||||||
|
|
||||||
|
expect(mockInsert).toHaveBeenCalledWith(lootdrops);
|
||||||
|
expect(mockValues).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
messageId: "msg1",
|
||||||
|
channelId: "chan1",
|
||||||
|
rewardAmount: 50,
|
||||||
|
currency: "GOLD"
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Message, TextChannel } from "discord.js";
|
|
||||||
import { getLootdropMessage } from "@/modules/economy/lootdrop.view";
|
|
||||||
import { config } from "@shared/lib/config";
|
import { config } from "@shared/lib/config";
|
||||||
import { economyService } from "./economy.service";
|
import { economyService } from "./economy.service";
|
||||||
import { terminalService } from "@shared/modules/terminal/terminal.service";
|
|
||||||
import { lootdrops } from "@db/schema";
|
import { lootdrops } from "@db/schema";
|
||||||
import { DrizzleClient } from "@shared/db/DrizzleClient";
|
import { DrizzleClient } from "@shared/db/DrizzleClient";
|
||||||
import { eq, and, isNull, lt } from "drizzle-orm";
|
import { eq, and, isNull, lt } from "drizzle-orm";
|
||||||
@@ -53,15 +50,16 @@ async function cleanupExpiredLootdrops(includeClaimed: boolean = false): Promise
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processMessage(message: Message) {
|
/**
|
||||||
if (message.author.bot || !message.guild) return;
|
* Track channel activity and determine if a lootdrop should spawn.
|
||||||
|
* Returns shouldSpawn: true if conditions are met (activity threshold + random chance).
|
||||||
const channelId = message.channel.id;
|
*/
|
||||||
|
function trackActivity(channelId: string): { shouldSpawn: boolean } {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Check cooldown
|
// Check cooldown
|
||||||
const cooldown = channelCooldowns.get(channelId);
|
const cooldown = channelCooldowns.get(channelId);
|
||||||
if (cooldown && now < cooldown) return;
|
if (cooldown && now < cooldown) return { shouldSpawn: false };
|
||||||
|
|
||||||
// Track activity
|
// Track activity
|
||||||
const timestamps = channelActivity.get(channelId) || [];
|
const timestamps = channelActivity.get(channelId) || [];
|
||||||
@@ -75,41 +73,61 @@ async function processMessage(message: Message) {
|
|||||||
if (recentActivity.length >= config.lootdrop.minMessages) {
|
if (recentActivity.length >= config.lootdrop.minMessages) {
|
||||||
// Chance to spawn
|
// Chance to spawn
|
||||||
if (Math.random() < config.lootdrop.spawnChance) {
|
if (Math.random() < config.lootdrop.spawnChance) {
|
||||||
await spawnLootdrop(message.channel as TextChannel);
|
|
||||||
// Set cooldown
|
// Set cooldown
|
||||||
channelCooldowns.set(channelId, now + config.lootdrop.cooldownMs);
|
channelCooldowns.set(channelId, now + config.lootdrop.cooldownMs);
|
||||||
channelActivity.set(channelId, []);
|
channelActivity.set(channelId, []);
|
||||||
|
return { shouldSpawn: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { shouldSpawn: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function spawnLootdrop(channel: TextChannel, overrideReward?: number, overrideCurrency?: string) {
|
/**
|
||||||
|
* Calculate lootdrop reward amount and currency.
|
||||||
|
*/
|
||||||
|
function calculateReward(overrideReward?: number, overrideCurrency?: string): { reward: number; currency: string } {
|
||||||
const min = config.lootdrop.reward.min;
|
const min = config.lootdrop.reward.min;
|
||||||
const max = config.lootdrop.reward.max;
|
const max = config.lootdrop.reward.max;
|
||||||
const reward = overrideReward ?? (Math.floor(Math.random() * (max - min + 1)) + min);
|
const reward = overrideReward ?? (Math.floor(Math.random() * (max - min + 1)) + min);
|
||||||
const currency = overrideCurrency ?? config.lootdrop.reward.currency;
|
const currency = overrideCurrency ?? config.lootdrop.reward.currency;
|
||||||
|
return { reward, currency };
|
||||||
|
}
|
||||||
|
|
||||||
const { content, files, components } = await getLootdropMessage(reward, currency);
|
/**
|
||||||
|
* Persist a spawned lootdrop to the database.
|
||||||
|
*/
|
||||||
|
async function persistLootdrop(messageId: string, channelId: string, reward: number, currency: string): Promise<void> {
|
||||||
|
await DrizzleClient.insert(lootdrops).values({
|
||||||
|
messageId,
|
||||||
|
channelId,
|
||||||
|
rewardAmount: reward,
|
||||||
|
currency: currency,
|
||||||
|
createdAt: new Date(),
|
||||||
|
// Expire after 10 mins
|
||||||
|
expiresAt: new Date(Date.now() + 600000)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a lootdrop from the database. Returns the channelId for Discord cleanup.
|
||||||
|
*/
|
||||||
|
async function removeLootdrop(messageId: string): Promise<{ channelId: string } | null> {
|
||||||
try {
|
try {
|
||||||
const message = await channel.send({ content, files, components });
|
// First fetch it to get channel info
|
||||||
|
const drop = await DrizzleClient.query.lootdrops.findFirst({
|
||||||
// Persist to DB
|
where: eq(lootdrops.messageId, messageId)
|
||||||
await DrizzleClient.insert(lootdrops).values({
|
|
||||||
messageId: message.id,
|
|
||||||
channelId: channel.id,
|
|
||||||
rewardAmount: reward,
|
|
||||||
currency: currency,
|
|
||||||
createdAt: new Date(),
|
|
||||||
// Expire after 10 mins
|
|
||||||
expiresAt: new Date(Date.now() + 600000)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger Terminal Update
|
if (!drop) return null;
|
||||||
terminalService.update(channel.guildId);
|
|
||||||
|
|
||||||
|
// Delete from DB
|
||||||
|
await DrizzleClient.delete(lootdrops).where(eq(lootdrops.messageId, messageId));
|
||||||
|
|
||||||
|
return { channelId: drop.channelId };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to spawn lootdrop:", error);
|
console.error("Error removing lootdrop:", error);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,9 +161,6 @@ async function tryClaim(messageId: string, userId: string, username: string): Pr
|
|||||||
`Claimed lootdrop in channel ${drop.channelId}`
|
`Claimed lootdrop in channel ${drop.channelId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Trigger Terminal Update (uses primary guild from env)
|
|
||||||
terminalService.update();
|
|
||||||
|
|
||||||
return { success: true, amount: drop.rewardAmount, currency: drop.currency };
|
return { success: true, amount: drop.rewardAmount, currency: drop.currency };
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -197,43 +212,13 @@ async function clearCaches() {
|
|||||||
console.log("[LootdropService] Caches cleared via administrative action.");
|
console.log("[LootdropService] Caches cleared via administrative action.");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteLootdrop(messageId: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
// First fetch it to get channel info so we can delete the message
|
|
||||||
const drop = await DrizzleClient.query.lootdrops.findFirst({
|
|
||||||
where: eq(lootdrops.messageId, messageId)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!drop) return false;
|
|
||||||
|
|
||||||
// Delete from DB
|
|
||||||
await DrizzleClient.delete(lootdrops).where(eq(lootdrops.messageId, messageId));
|
|
||||||
|
|
||||||
// Try to delete from Discord
|
|
||||||
try {
|
|
||||||
const { AuroraClient } = await import("../../../bot/lib/BotClient");
|
|
||||||
const channel = await AuroraClient.channels.fetch(drop.channelId) as TextChannel;
|
|
||||||
if (channel) {
|
|
||||||
const message = await channel.messages.fetch(messageId);
|
|
||||||
if (message) await message.delete();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Could not delete lootdrop message from Discord:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error deleting lootdrop:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const lootdropService = {
|
export const lootdropService = {
|
||||||
cleanupExpiredLootdrops,
|
cleanupExpiredLootdrops,
|
||||||
processMessage,
|
trackActivity,
|
||||||
spawnLootdrop,
|
calculateReward,
|
||||||
|
persistLootdrop,
|
||||||
|
removeLootdrop,
|
||||||
tryClaim,
|
tryClaim,
|
||||||
getLootdropState,
|
getLootdropState,
|
||||||
clearCaches,
|
clearCaches,
|
||||||
deleteLootdrop,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export const inventoryService = {
|
|||||||
const results: any[] = [];
|
const results: any[] = [];
|
||||||
|
|
||||||
// 2. Apply Effects
|
// 2. Apply Effects
|
||||||
const { validateAndExecuteEffect } = await import("@/modules/inventory/effect.registry");
|
const { validateAndExecuteEffect } = await import("./effect.registry");
|
||||||
|
|
||||||
for (const effect of usageData.effects) {
|
for (const effect of usageData.effects) {
|
||||||
const result = await validateAndExecuteEffect(effect, userId, txFn);
|
const result = await validateAndExecuteEffect(effect, userId, txFn);
|
||||||
|
|||||||
Reference in New Issue
Block a user