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:
@@ -1,8 +1,5 @@
|
||||
import { Message, TextChannel } from "discord.js";
|
||||
import { getLootdropMessage } from "@/modules/economy/lootdrop.view";
|
||||
import { config } from "@shared/lib/config";
|
||||
import { economyService } from "./economy.service";
|
||||
import { terminalService } from "@shared/modules/terminal/terminal.service";
|
||||
import { lootdrops } from "@db/schema";
|
||||
import { DrizzleClient } from "@shared/db/DrizzleClient";
|
||||
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;
|
||||
|
||||
const channelId = message.channel.id;
|
||||
/**
|
||||
* Track channel activity and determine if a lootdrop should spawn.
|
||||
* Returns shouldSpawn: true if conditions are met (activity threshold + random chance).
|
||||
*/
|
||||
function trackActivity(channelId: string): { shouldSpawn: boolean } {
|
||||
const now = Date.now();
|
||||
|
||||
// Check cooldown
|
||||
const cooldown = channelCooldowns.get(channelId);
|
||||
if (cooldown && now < cooldown) return;
|
||||
if (cooldown && now < cooldown) return { shouldSpawn: false };
|
||||
|
||||
// Track activity
|
||||
const timestamps = channelActivity.get(channelId) || [];
|
||||
@@ -75,41 +73,61 @@ async function processMessage(message: Message) {
|
||||
if (recentActivity.length >= config.lootdrop.minMessages) {
|
||||
// Chance to spawn
|
||||
if (Math.random() < config.lootdrop.spawnChance) {
|
||||
await spawnLootdrop(message.channel as TextChannel);
|
||||
// Set cooldown
|
||||
channelCooldowns.set(channelId, now + config.lootdrop.cooldownMs);
|
||||
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 max = config.lootdrop.reward.max;
|
||||
const reward = overrideReward ?? (Math.floor(Math.random() * (max - min + 1)) + min);
|
||||
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 {
|
||||
const message = await channel.send({ content, files, components });
|
||||
|
||||
// Persist to DB
|
||||
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)
|
||||
// First fetch it to get channel info
|
||||
const drop = await DrizzleClient.query.lootdrops.findFirst({
|
||||
where: eq(lootdrops.messageId, messageId)
|
||||
});
|
||||
|
||||
// Trigger Terminal Update
|
||||
terminalService.update(channel.guildId);
|
||||
if (!drop) return null;
|
||||
|
||||
// Delete from DB
|
||||
await DrizzleClient.delete(lootdrops).where(eq(lootdrops.messageId, messageId));
|
||||
|
||||
return { channelId: drop.channelId };
|
||||
} 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}`
|
||||
);
|
||||
|
||||
// Trigger Terminal Update (uses primary guild from env)
|
||||
terminalService.update();
|
||||
|
||||
return { success: true, amount: drop.rewardAmount, currency: drop.currency };
|
||||
|
||||
} catch (error) {
|
||||
@@ -197,43 +212,13 @@ async function clearCaches() {
|
||||
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 = {
|
||||
cleanupExpiredLootdrops,
|
||||
processMessage,
|
||||
spawnLootdrop,
|
||||
trackActivity,
|
||||
calculateReward,
|
||||
persistLootdrop,
|
||||
removeLootdrop,
|
||||
tryClaim,
|
||||
getLootdropState,
|
||||
clearCaches,
|
||||
deleteLootdrop,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user